82 Commits

Author SHA1 Message Date
mr
ef916fe2d9 InitAPI 2026-02-03 16:05:31 +01:00
mr
4258f6b580 missing import 2026-02-03 16:01:23 +01:00
mr
06ad584b6a missing import 2026-02-03 16:01:15 +01:00
mr
c650f08b8e add setup 2026-02-03 15:57:12 +01:00
mr
c90b55f312 Beego set up 2026-02-03 15:56:39 +01:00
mr
4f28b9b589 loop on discovery 2026-02-03 09:37:53 +01:00
mr
30e6c9a618 bug on enums 2026-02-03 08:44:47 +01:00
mr
186ba3e689 address 2026-01-29 13:20:33 +01:00
mr
c1519f6b26 update peer model 2026-01-29 13:12:15 +01:00
mr
97cf629e27 moove user from propalgation to Nats Response 2026-01-28 17:27:02 +01:00
mr
c0d89ea9e1 utils to transform byte to resource 2026-01-28 17:04:40 +01:00
mr
7ccd7fe16b CATALOG_SEARCH_EVENT 2026-01-28 17:00:59 +01:00
mr
886f9d15ba include datatype to propalgationMessage 2026-01-28 16:51:27 +01:00
mr
d26789d64e Propalgation Mess 2026-01-28 16:44:47 +01:00
mr
7911cf29de Update NATS channel allowed 2026-01-28 16:29:19 +01:00
mr
743f4a6ff7 share pubsub action 2026-01-28 16:22:42 +01:00
mr
d2c5d20318 Lightest AdminRequest 2026-01-28 15:52:48 +01:00
mr
e3fe49c239 Structured NATS 2026-01-28 15:49:21 +01:00
mr
1c9d7b63c0 NATSResponse 2026-01-28 15:08:07 +01:00
mr
d098d253d8 NATSMethod 2026-01-28 15:06:32 +01:00
mr
ecb734187a NATSResponse 2026-01-28 15:05:48 +01:00
mr
3c052bf165 Peer Accessor modify 2026-01-27 15:37:28 +01:00
mr
9af8d15672 DISCOVERY 2026-01-27 12:49:51 +01:00
mr
4d57767005 better implem obj hierarchy 2026-01-27 11:14:28 +01:00
mr
9b4f9e420a Mongo Improvment for filtering 2026-01-27 10:59:38 +01:00
mr
d772a703da adjust useless line 2026-01-27 09:50:07 +01:00
mr
c69069449f chan name fail 2026-01-27 09:37:51 +01:00
mr
643beacd4b Configuration NATS 2026-01-27 09:35:47 +01:00
mr
802786daa7 Peer Manipulation 2026-01-26 16:29:09 +01:00
mr
b35d4e2b36 url 2026-01-26 14:34:48 +01:00
mr
1f93493965 relation 2026-01-26 14:32:56 +01:00
mr
055e6c70cd filter peer 2026-01-26 13:00:55 +01:00
mr
85a8857938 oclib -> Generate Peer ID 2026-01-26 12:34:04 +01:00
mr
bc94f2b188 Adjust Mongo 2026-01-26 10:36:15 +01:00
mr
6b12aa1713 silence InitNative 2026-01-23 10:03:29 +01:00
mr
f3d7c65b18 partner 2026-01-23 07:51:15 +01:00
mr
d06c9e9337 GetRelationPath 2026-01-23 07:48:04 +01:00
mr
0308b4ea10 add func 2026-01-23 07:45:36 +01:00
mr
b71b1e741d adjust peer element for partnership 2026-01-23 07:38:20 +01:00
mr
8f5f3e331d PENDING status 2026-01-22 16:11:54 +01:00
mr
2d1d76767c keep only verify 2026-01-22 15:58:38 +01:00
mr
00bcca379f Peer Evolve 2026-01-22 15:55:27 +01:00
mr
b987286759 NATS URL 2026-01-22 14:18:02 +01:00
mr
c72954d2f7 no needed group 2026-01-22 13:58:21 +01:00
mr
bcfd43e140 group.Group 2026-01-22 13:45:19 +01:00
mr
a4512e4da6 add Groups 2026-01-22 13:38:55 +01:00
mr
1c3b9218f7 correction on bookig flow 2026-01-15 13:27:57 +01:00
mr
7c5d5c491f Better peer filtering 2026-01-15 12:26:56 +01:00
mr
76eb167c5b peer improvment 2026-01-15 12:15:04 +01:00
mr
fa5b754333 add Selected in Workflow Exec 2026-01-14 13:57:49 +01:00
mr
0e378dc19c add Selected in Workflow Exec 2026-01-14 13:55:32 +01:00
mr
5cdfc28d2f delete scheduler 2026-01-13 16:53:25 +01:00
mr
6d745fe922 add event base intelligency 2026-01-13 16:04:31 +01:00
mr
c35b06e0bc add NATS EVENT 2026-01-12 15:40:37 +01:00
mr
be770ec763 multipart file over os.file 2026-01-12 14:26:29 +01:00
mr
27f295f17e Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib
Merged Main OCLIB
2026-01-12 12:00:32 +01:00
mr
33b8d2799a update import plantUML 2026-01-12 11:59:05 +01:00
188b758f7a Ajouter .gitattributes 2025-11-01 16:39:25 +01:00
pb
f4b0cf5683 changed the value used as the key in the related map, using the 'node id' was erasing some relations when a a processing is linked to two storages 2025-08-08 16:15:53 +02:00
pb
e7a71188a3 changed the value used as the key in the related map, using the 'node id' was erasing some relations when a a processing is linked to two storages 2025-08-08 16:05:36 +02:00
pb
40a61387b9 added the UUID of the peer when an error appears 2025-08-05 13:39:21 +02:00
pb
cc939451fd added the UUID of the peer when an error appears 2025-08-05 13:25:47 +02:00
pb
76e9b2562e added the enum to reach the /minio/secet route 2025-08-05 11:56:27 +02:00
pb
cc3091d401 added the function to load one ressource for each ressource type 2025-07-31 15:53:05 +02:00
pb
3ddbf1a967 added comments 2025-07-30 18:28:19 +02:00
pb
be2a1cc114 corrction of the non initialized map 2025-07-30 18:21:09 +02:00
pb
a093369dc5 corrction of the non initialized map 2025-07-30 18:15:55 +02:00
pb
76d83878eb added a method to workflow which allows to retrieve items by resource type and resource id 2025-07-30 18:01:23 +02:00
pb
e735f78e58 added a new method to create kubernetes names with the same naming 2025-07-15 14:58:19 +02:00
pb
98a2359c9d added a tweak to PeerItemOrder GetPrice in order to not crash on nil Purchase 2025-07-10 11:47:54 +02:00
pb
83e590d4e1 added error handling 2025-07-09 17:42:37 +02:00
pb
4e3ff9aa08 Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib 2025-07-09 16:54:37 +02:00
pb
424d523c5e added the bson tag for DestPeerId which was causing error when deserializing from mongo results 2025-07-09 16:50:41 +02:00
mr
346275e12c test 2025-07-08 13:59:55 +02:00
mr
6ab774cc43 Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib into main
test
2025-07-08 13:42:28 +02:00
mr
2748b59221 test booking 2025-07-08 13:42:13 +02:00
pb
34f01e9740 Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib 2025-07-08 12:02:34 +02:00
pb
dcdc6ff1d9 added more info in GenericStoreOne error generated by validate() 2025-07-08 12:02:18 +02:00
pb
365b924e4b Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib 2025-07-07 16:30:58 +02:00
pb
e7e56d1859 commented the PricingProfile check for IsBooked 2025-07-07 16:30:29 +02:00
mr
443546027b Merge branch 'main' of https://cloud.o-forge.io/core/oc-lib into main 2025-07-04 10:44:59 +02:00
mr
1c4f3f756f test 2025-07-04 10:44:14 +02:00
62 changed files with 1837 additions and 1093 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
# Force Go as the main language
*.go linguist-detectable=true
* linguist-language=Go

View File

@@ -9,6 +9,7 @@ import "sync"
// =================================================== // ===================================================
type Config struct { type Config struct {
APIPort int
NATSUrl string NATSUrl string
MongoUrl string MongoUrl string
MongoDatabase string MongoDatabase string
@@ -17,6 +18,8 @@ type Config struct {
LokiUrl string LokiUrl string
LogLevel string LogLevel string
Whitelist bool Whitelist bool
PrivateKeyPath string
PublicKeyPath string
} }
func (c Config) GetUrl() string { func (c Config) GetUrl() string {
@@ -37,12 +40,13 @@ func GetConfig() *Config {
return instance return instance
} }
func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string) *Config { func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string, port int) *Config {
GetConfig().MongoUrl = mongoUrl GetConfig().MongoUrl = mongoUrl
GetConfig().MongoDatabase = database GetConfig().MongoDatabase = database
GetConfig().NATSUrl = natsUrl GetConfig().NATSUrl = natsUrl
GetConfig().LokiUrl = lokiUrl GetConfig().LokiUrl = lokiUrl
GetConfig().LogLevel = logLevel GetConfig().LogLevel = logLevel
GetConfig().Whitelist = true GetConfig().Whitelist = true
GetConfig().APIPort = port
return GetConfig() return GetConfig()
} }

View File

@@ -2,6 +2,7 @@ package dbs
import ( import (
"fmt" "fmt"
"runtime/debug"
"strings" "strings"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
@@ -19,6 +20,8 @@ const (
GT GT
EQUAL EQUAL
NOT NOT
ELEMMATCH
OR
) )
var str = [...]string{ var str = [...]string{
@@ -31,113 +34,43 @@ var str = [...]string{
"gt", "gt",
"equal", "equal",
"not", "not",
"elemMatch",
} }
func (m Operator) String() string { func (m Operator) String() string {
return str[m] return str[m]
} }
func (m Operator) ToMongoEOperator(k string, value interface{}) bson.E {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered. Error:\n", r)
}
}()
defaultValue := bson.E{Key: k, Value: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}}
switch m {
case LIKE:
return bson.E{Key: k, Value: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}}
case EXISTS:
return bson.E{Key: k, Value: bson.M{"$exists": ToValueOperator(StringToOperator(m.String()), value)}}
case IN:
return bson.E{Key: k, Value: bson.M{"$in": ToValueOperator(StringToOperator(m.String()), value)}}
case GTE:
return bson.E{Key: k, Value: bson.M{"$gte": ToValueOperator(StringToOperator(m.String()), value)}}
case GT:
return bson.E{Key: k, Value: bson.M{"$gt": ToValueOperator(StringToOperator(m.String()), value)}}
case LTE:
return bson.E{Key: k, Value: bson.M{"$lte": ToValueOperator(StringToOperator(m.String()), value)}}
case LT:
return bson.E{Key: k, Value: bson.M{"$lt": ToValueOperator(StringToOperator(m.String()), value)}}
case EQUAL:
return bson.E{Key: k, Value: value}
case NOT:
v := value.(Filters)
orList := bson.A{}
andList := bson.A{}
f := bson.D{}
for k, filter := range v.Or {
for _, ff := range filter {
orList = append(orList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
}
}
for k, filter := range v.And {
for _, ff := range filter {
andList = append(andList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
}
}
if len(orList) > 0 && len(andList) == 0 {
f = bson.D{{"$or", orList}}
} else {
if len(orList) > 0 {
andList = append(andList, bson.M{"$or": orList})
}
f = bson.D{{"$and", andList}}
}
return bson.E{Key: "$not", Value: f}
default:
return defaultValue
}
}
func (m Operator) ToMongoOperator(k string, value interface{}) bson.M { func (m Operator) ToMongoOperator(k string, value interface{}) bson.M {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
fmt.Println("Recovered. Error:\n", r) fmt.Println("Recovered. Error:\n", r, debug.Stack())
} }
}() }()
defaultValue := bson.M{k: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}} defaultValue := bson.M{k: bson.M{"$regex": m.ToValueOperator(StringToOperator(m.String()), value)}}
switch m { switch m {
case LIKE: case LIKE:
return bson.M{k: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$regex": m.ToValueOperator(StringToOperator(m.String()), value)}}
case EXISTS: case EXISTS:
return bson.M{k: bson.M{"$exists": ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$exists": m.ToValueOperator(StringToOperator(m.String()), value)}}
case IN: case IN:
return bson.M{k: bson.M{"$in": ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$in": m.ToValueOperator(StringToOperator(m.String()), value)}}
case GTE: case GTE:
return bson.M{k: bson.M{"$gte": ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$gte": m.ToValueOperator(StringToOperator(m.String()), value)}}
case GT: case GT:
return bson.M{k: bson.M{"$gt": ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$gt": m.ToValueOperator(StringToOperator(m.String()), value)}}
case LTE: case LTE:
return bson.M{k: bson.M{"$lte": ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$lte": m.ToValueOperator(StringToOperator(m.String()), value)}}
case LT: case LT:
return bson.M{k: bson.M{"$lt": ToValueOperator(StringToOperator(m.String()), value)}} return bson.M{k: bson.M{"$lt": m.ToValueOperator(StringToOperator(m.String()), value)}}
case ELEMMATCH:
return bson.M{k: bson.M{"$elemMatch": m.ToValueOperator(StringToOperator(m.String()), value)}}
case EQUAL: case EQUAL:
return bson.M{k: value} return bson.M{k: value}
case NOT: case NOT:
v := value.(Filters) return bson.M{"$not": m.ToValueOperator(StringToOperator(m.String()), value)}
orList := bson.A{} case OR:
andList := bson.A{} return bson.M{"$or": m.ToValueOperator(StringToOperator(m.String()), value)}
f := bson.D{}
for k, filter := range v.Or {
for _, ff := range filter {
orList = append(orList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
}
}
for k, filter := range v.And {
for _, ff := range filter {
andList = append(andList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
}
}
if len(orList) > 0 && len(andList) == 0 {
f = bson.D{{"$or", orList}}
} else {
if len(orList) > 0 {
andList = append(andList, bson.M{"$or": orList})
}
f = bson.D{{"$and", andList}}
}
return bson.M{"$not": f}
default: default:
return defaultValue return defaultValue
} }
@@ -152,13 +85,46 @@ func StringToOperator(s string) Operator {
return LIKE return LIKE
} }
func ToValueOperator(operator Operator, value interface{}) interface{} { func GetBson(filters *Filters) bson.D {
f := bson.D{}
orList := bson.A{}
andList := bson.A{}
if filters != nil {
for k, filter := range filters.Or {
for _, ff := range filter {
orList = append(orList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
}
}
for k, filter := range filters.And {
for _, ff := range filter {
andList = append(andList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
}
}
if len(orList) > 0 && len(andList) == 0 {
f = bson.D{{"$or", orList}}
} else {
if len(orList) > 0 {
andList = append(andList, bson.M{"$or": orList})
}
f = bson.D{{"$and", andList}}
}
}
return f
}
func (m Operator) ToValueOperator(operator Operator, value interface{}) interface{} {
switch value := value.(type) {
case *Filters:
return GetBson(value)
default:
if strings.TrimSpace(fmt.Sprintf("%v", value)) == "*" { if strings.TrimSpace(fmt.Sprintf("%v", value)) == "*" {
value = "" value = ""
} }
if operator == LIKE { if operator == LIKE {
return "(?i).*" + strings.TrimSpace(fmt.Sprintf("%v", value)) + ".*" return "(?i).*" + strings.TrimSpace(fmt.Sprintf("%v", value)) + ".*"
} }
}
return value return value
} }

View File

@@ -289,29 +289,8 @@ func (m *MongoDB) Search(filters *dbs.Filters, collection_name string) (*mongo.C
opts := options.Find() opts := options.Find()
opts.SetLimit(1000) opts.SetLimit(1000)
targetDBCollection := CollectionMap[collection_name] targetDBCollection := CollectionMap[collection_name]
orList := bson.A{}
andList := bson.A{} f := dbs.GetBson(filters)
f := bson.D{}
if filters != nil {
for k, filter := range filters.Or {
for _, ff := range filter {
orList = append(orList, dbs.StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
}
}
for k, filter := range filters.And {
for _, ff := range filter {
andList = append(andList, dbs.StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
}
}
if len(orList) > 0 && len(andList) == 0 {
f = bson.D{{"$or", orList}}
} else {
if len(orList) > 0 {
andList = append(andList, bson.M{"$or": orList})
}
f = bson.D{{"$and", andList}}
}
}
MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second) MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
// defer cancel() // defer cancel()

View File

@@ -194,7 +194,7 @@ AccessPricingProfile ^-- ProcessingResourcePricingProfile
ExploitPricingProfile ^-- ComputeResourcePricingProfile ExploitPricingProfile ^-- ComputeResourcePricingProfile
ExploitPricingProfile ^-- StorageResourcePricingProfile ExploitPricingProfile ^-- StorageResourcePricingProfile
interface PricingProfileITF { interface PricingProfileITF {
GetPrice(quantity float64, val float64, start date, end date, request) float64 GetPriceHT(quantity float64, val float64, start date, end date, request) float64
IsPurchased() bool IsPurchased() bool
} }
class AccessPricingProfile { class AccessPricingProfile {
@@ -319,7 +319,7 @@ Workflow "1 " --* "many " ExploitResourceSet
class Workflow {} class Workflow {}
interface PricedItemITF { interface PricedItemITF {
getPrice(request) float64, error GetPriceHT(request) float64, error
} }
@enduml @enduml

View File

@@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"os"
"strings" "strings"
"runtime/debug" "runtime/debug"
@@ -30,6 +31,8 @@ import (
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
beego "github.com/beego/beego/v2/server/web" beego "github.com/beego/beego/v2/server/web"
"github.com/beego/beego/v2/server/web/context" "github.com/beego/beego/v2/server/web/context"
"github.com/beego/beego/v2/server/web/filter/cors"
"github.com/google/uuid"
"github.com/goraz/onion" "github.com/goraz/onion"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
@@ -57,8 +60,36 @@ const (
LIVE_DATACENTER = tools.LIVE_DATACENTER LIVE_DATACENTER = tools.LIVE_DATACENTER
LIVE_STORAGE = tools.LIVE_STORAGE LIVE_STORAGE = tools.LIVE_STORAGE
PURCHASE_RESOURCE = tools.PURCHASE_RESOURCE PURCHASE_RESOURCE = tools.PURCHASE_RESOURCE
NATIVE_TOOL = tools.NATIVE_TOOL
) )
func GetMySelf() (string, error) {
return utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{Admin: true}))
}
func IsMySelf(peerID string) (bool, string) {
return utils.IsMySelf(peerID, (&peer.Peer{}).GetAccessor(&tools.APIRequest{Admin: true}))
}
func GenerateNodeID() (string, error) {
folderStatic := "/var/lib/opencloud-node"
if _, err := os.Stat(folderStatic); err == nil {
os.MkdirAll(folderStatic, 0644)
}
folderStatic += "/node_id"
if _, err := os.Stat(folderStatic); os.IsNotExist(err) {
hostname, err := os.Hostname()
if err != nil {
return "", err
}
id := uuid.NewSHA1(uuid.NameSpaceOID, []byte("oc-"+hostname))
err = os.WriteFile(folderStatic, []byte(id.String()), 0644)
return id.String(), err
}
data, err := os.ReadFile(folderStatic)
return string(data), err
}
// will turn into standards api hostnames // will turn into standards api hostnames
func (d LibDataEnum) API() string { func (d LibDataEnum) API() string {
return tools.DefaultAPI[d] return tools.DefaultAPI[d]
@@ -103,12 +134,13 @@ type LibData struct {
} }
func InitDaemon(appName string) { func InitDaemon(appName string) {
beego.BConfig.AppName = appName
config.SetAppName(appName) // set the app name to the logger to define the main log chan config.SetAppName(appName) // set the app name to the logger to define the main log chan
// create a temporary console logger for init // create a temporary console logger for init
logs.SetLogger(logs.CreateLogger("main")) logs.SetLogger(logs.CreateLogger("main"))
// Load the right config file // Load the right config file
o := GetConfLoader() o := GetConfLoader()
// resources.InitNative()
// feed the library with the loaded config // feed the library with the loaded config
SetConfig( SetConfig(
o.GetStringDefault("MONGO_URL", "mongodb://127.0.0.1:27017"), o.GetStringDefault("MONGO_URL", "mongodb://127.0.0.1:27017"),
@@ -116,6 +148,7 @@ func InitDaemon(appName string) {
o.GetStringDefault("NATS_URL", "nats://localhost:4222"), o.GetStringDefault("NATS_URL", "nats://localhost:4222"),
o.GetStringDefault("LOKI_URL", ""), o.GetStringDefault("LOKI_URL", ""),
o.GetStringDefault("LOG_LEVEL", "info"), o.GetStringDefault("LOG_LEVEL", "info"),
o.GetIntDefault("API_PORT", 8080),
) )
// Beego init // Beego init
beego.BConfig.AppName = appName beego.BConfig.AppName = appName
@@ -167,10 +200,22 @@ func ExtractTokenInfo(request http.Request) (string, string, []string) {
return "", "", []string{} return "", "", []string{}
} }
func Init(appName string) { func InitAPI(appName string) {
InitDaemon(appName) InitDaemon(appName)
beego.BConfig.Listen.HTTPPort = config.GetConfig().APIPort
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
c := cors.Allow(&cors.Options{
AllowAllOrigins: true,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Authorization", "Content-Type"},
ExposeHeaders: []string{"Content-Length", "Content-Type"},
AllowCredentials: true,
})
beego.InsertFilter("*", beego.BeforeRouter, c)
api := &tools.API{} api := &tools.API{}
api.Discovered(beego.BeeApp.Handlers.GetAllControllerInfo()) api.Discovered(beego.BeeApp.Handlers.GetAllControllerInfo())
beego.Run()
} }
// //
@@ -192,8 +237,8 @@ func GetLogger() zerolog.Logger {
* @param logLevel string * @param logLevel string
* @return *Config * @return *Config
*/ */
func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string) *config.Config { func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string, port int) *config.Config {
cfg := config.SetConfig(mongoUrl, database, natsUrl, lokiUrl, logLevel) cfg := config.SetConfig(mongoUrl, database, natsUrl, lokiUrl, logLevel, port)
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in Init : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack()))) tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in Init : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
@@ -232,63 +277,33 @@ func GetConfLoader() *onion.Onion {
} }
type Request struct { type Request struct {
collection LibDataEnum Collection LibDataEnum
user string User string
peerID string PeerID string
groups []string Groups []string
caller *tools.HTTPCaller Caller *tools.HTTPCaller
admin bool
} }
func NewRequest(collection LibDataEnum, user string, peerID string, groups []string, caller *tools.HTTPCaller) *Request { func NewRequest(collection LibDataEnum, user string, peerID string, groups []string, caller *tools.HTTPCaller) *Request {
return &Request{collection: collection, user: user, peerID: peerID, groups: groups, caller: caller} return &Request{Collection: collection, User: user, PeerID: peerID, Groups: groups, Caller: caller}
} }
func ToScheduler(m interface{}) (n *workflow_execution.WorkflowSchedule) { func NewRequestAdmin(collection LibDataEnum, caller *tools.HTTPCaller) *Request {
defer func() { return &Request{Collection: collection, Caller: caller, admin: true}
if r := recover(); r != nil {
return
}
}()
return m.(*workflow_execution.WorkflowSchedule)
}
func (r *Request) Schedule(wfID string, scheduler *workflow_execution.WorkflowSchedule) (*workflow_execution.WorkflowSchedule, error) {
ws, _, _, err := scheduler.Schedules(wfID, &tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
})
if err != nil {
return nil, err
}
return ws, nil
}
func (r *Request) CheckBooking(wfID string, start string, end string, durationInS float64, cron string) bool {
ok, _, _, _, _, err := workflow_execution.NewScheduler(start, end, durationInS, cron).GetBuyAndBook(wfID, &tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
})
if err != nil {
fmt.Println(err)
return false
}
return ok
} }
/*
func (r *Request) PaymentTunnel(o *order.Order, scheduler *workflow_execution.WorkflowSchedule) error { func (r *Request) PaymentTunnel(o *order.Order, scheduler *workflow_execution.WorkflowSchedule) error {
/*return o.Pay(scheduler, &tools.APIRequest{ return o.Pay(scheduler, &tools.APIRequest{
Caller: r.caller, Caller: r.caller,
Username: r.user, Username: r.user,
PeerID: r.peerID, PeerID: r.peerID,
Groups: r.groups, Groups: r.groups,
})*/ })
return nil return nil
} }
*/
/* /*
* Search will search for the data in the database * Search will search for the data in the database
* @param filters *dbs.Filters * @param filters *dbs.Filters
@@ -304,11 +319,12 @@ func (r *Request) Search(filters *dbs.Filters, word string, isDraft bool) (data
data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{ d, code, err := models.Model(r.Collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller, Caller: r.Caller,
Username: r.user, Username: r.User,
PeerID: r.peerID, PeerID: r.PeerID,
Groups: r.groups, Groups: r.Groups,
Admin: r.admin,
}).Search(filters, word, isDraft) }).Search(filters, word, isDraft)
if err != nil { if err != nil {
data = LibDataShallow{Data: d, Code: code, Err: err.Error()} data = LibDataShallow{Data: d, Code: code, Err: err.Error()}
@@ -331,11 +347,12 @@ func (r *Request) LoadAll(isDraft bool) (data LibDataShallow) {
data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{ d, code, err := models.Model(r.Collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller, Caller: r.Caller,
Username: r.user, Username: r.User,
PeerID: r.peerID, PeerID: r.PeerID,
Groups: r.groups, Groups: r.Groups,
Admin: r.admin,
}).LoadAll(isDraft) }).LoadAll(isDraft)
if err != nil { if err != nil {
data = LibDataShallow{Data: d, Code: code, Err: err.Error()} data = LibDataShallow{Data: d, Code: code, Err: err.Error()}
@@ -359,11 +376,12 @@ func (r *Request) LoadOne(id string) (data LibData) {
data = LibData{Data: nil, Code: 500, Err: "Panic recovered in LoadOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibData{Data: nil, Code: 500, Err: "Panic recovered in LoadOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{ d, code, err := models.Model(r.Collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller, Caller: r.Caller,
Username: r.user, Username: r.User,
PeerID: r.peerID, PeerID: r.PeerID,
Groups: r.groups, Groups: r.Groups,
Admin: r.admin,
}).LoadOne(id) }).LoadOne(id)
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
@@ -388,12 +406,13 @@ func (r *Request) UpdateOne(set map[string]interface{}, id string) (data LibData
data = LibData{Data: nil, Code: 500, Err: "Panic recovered in UpdateOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibData{Data: nil, Code: 500, Err: "Panic recovered in UpdateOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
model := models.Model(r.collection.EnumIndex()) model := models.Model(r.Collection.EnumIndex())
d, code, err := model.GetAccessor(&tools.APIRequest{ d, code, err := model.GetAccessor(&tools.APIRequest{
Caller: r.caller, Caller: r.Caller,
Username: r.user, Username: r.User,
PeerID: r.peerID, PeerID: r.PeerID,
Groups: r.groups, Groups: r.Groups,
Admin: r.admin,
}).UpdateOne(model.Deserialize(set, model), id) }).UpdateOne(model.Deserialize(set, model), id)
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
@@ -417,11 +436,12 @@ func (r *Request) DeleteOne(id string) (data LibData) {
data = LibData{Data: nil, Code: 500, Err: "Panic recovered in DeleteOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibData{Data: nil, Code: 500, Err: "Panic recovered in DeleteOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{ d, code, err := models.Model(r.Collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller, Caller: r.Caller,
Username: r.user, Username: r.User,
PeerID: r.peerID, PeerID: r.PeerID,
Groups: r.groups, Groups: r.Groups,
Admin: r.admin,
}).DeleteOne(id) }).DeleteOne(id)
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
@@ -445,12 +465,13 @@ func (r *Request) StoreOne(object map[string]interface{}) (data LibData) {
data = LibData{Data: nil, Code: 500, Err: "Panic recovered in StoreOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibData{Data: nil, Code: 500, Err: "Panic recovered in StoreOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
model := models.Model(r.collection.EnumIndex()) model := models.Model(r.Collection.EnumIndex())
d, code, err := model.GetAccessor(&tools.APIRequest{ d, code, err := model.GetAccessor(&tools.APIRequest{
Caller: r.caller, Caller: r.Caller,
Username: r.user, Username: r.User,
PeerID: r.peerID, PeerID: r.PeerID,
Groups: r.groups, Groups: r.Groups,
Admin: r.admin,
}).StoreOne(model.Deserialize(object, model)) }).StoreOne(model.Deserialize(object, model))
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
@@ -474,12 +495,13 @@ func (r *Request) CopyOne(object map[string]interface{}) (data LibData) {
data = LibData{Data: nil, Code: 500, Err: "Panic recovered in UpdateOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} data = LibData{Data: nil, Code: 500, Err: "Panic recovered in UpdateOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
} }
}() }()
model := models.Model(r.collection.EnumIndex()) model := models.Model(r.Collection.EnumIndex())
d, code, err := model.GetAccessor(&tools.APIRequest{ d, code, err := model.GetAccessor(&tools.APIRequest{
Caller: r.caller, Caller: r.Caller,
Username: r.user, Username: r.User,
PeerID: r.peerID, PeerID: r.PeerID,
Groups: r.groups, Groups: r.Groups,
Admin: r.admin,
}).CopyOne(model.Deserialize(object, model)) }).CopyOne(model.Deserialize(object, model))
if err != nil { if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()} data = LibData{Data: d, Code: code, Err: err.Error()}
@@ -597,3 +619,64 @@ func (l *LibData) ToPurchasedResource() *purchase_resource.PurchaseResource {
} }
return nil return nil
} }
// ============== ADMIRALTY ==============
// Returns a concatenation of the peerId and namespace in order for
// kubernetes ressources to have a unique name, under 63 characters
// and yet identify which peer they are created for
func GetConcatenatedName(peerId string, namespace string) string {
s := strings.Split(namespace, "-")[:2]
n := s[0] + "-" + s[1]
return peerId + "-" + n
}
// ------------- Loading resources ----------
func LoadOneStorage(storageId string, user string, peerID string, groups []string) (*resources.StorageResource, error) {
res := NewRequest(LibDataEnum(STORAGE_RESOURCE), user, peerID, groups, nil).LoadOne(storageId)
if res.Code != 200 {
l := GetLogger()
l.Error().Msg("Error while loading storage ressource " + storageId)
return nil, fmt.Errorf(res.Err)
}
return res.ToStorageResource(), nil
}
func LoadOneComputing(computingId string, user string, peerID string, groups []string) (*resources.ComputeResource, error) {
res := NewRequest(LibDataEnum(COMPUTE_RESOURCE), user, peerID, groups, nil).LoadOne(computingId)
if res.Code != 200 {
l := GetLogger()
l.Error().Msg("Error while loading computing ressource " + computingId)
return nil, fmt.Errorf(res.Err)
}
return res.ToComputeResource(), nil
}
func LoadOneProcessing(processingId string, user string, peerID string, groups []string) (*resources.ProcessingResource, error) {
res := NewRequest(LibDataEnum(PROCESSING_RESOURCE), user, peerID, groups, nil).LoadOne(processingId)
if res.Code != 200 {
l := GetLogger()
l.Error().Msg("Error while loading processing ressource " + processingId)
return nil, fmt.Errorf(res.Err)
}
return res.ToProcessingResource(), nil
}
func LoadOneData(dataId string, user string, peerID string, groups []string) (*resources.DataResource, error) {
res := NewRequest(LibDataEnum(DATA_RESOURCE), user, peerID, groups, nil).LoadOne(dataId)
if res.Code != 200 {
l := GetLogger()
l.Error().Msg("Error while loading data ressource " + dataId)
return nil, fmt.Errorf(res.Err)
}
return res.ToDataResource(), nil
}

2
go.mod
View File

@@ -8,7 +8,6 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/goraz/onion v0.1.3 github.com/goraz/onion v0.1.3
github.com/nats-io/nats.go v1.37.0 github.com/nats-io/nats.go v1.37.0
github.com/robfig/cron/v3 v3.0.1
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
) )
@@ -46,7 +45,6 @@ require (
github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect
github.com/robfig/cron v1.2.0
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/scram v1.1.2 // indirect

6
go.sum
View File

@@ -89,10 +89,6 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
@@ -109,8 +105,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=

View File

@@ -1,14 +1,16 @@
package bill package bill
import ( import (
"encoding/json"
"fmt"
"sync" "sync"
"time" "time"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/common/enum" "cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/order" "cloud.o-forge.io/core/oc-lib/models/order"
"cloud.o-forge.io/core/oc-lib/models/peer" "cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/resources"
"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource" "cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
@@ -135,20 +137,25 @@ type PeerOrder struct {
} }
func (d *PeerOrder) Pay(request *tools.APIRequest, response chan *PeerOrder, wg *sync.WaitGroup) { func (d *PeerOrder) Pay(request *tools.APIRequest, response chan *PeerOrder, wg *sync.WaitGroup) {
d.Status = enum.PENDING d.Status = enum.PENDING
go func() { go func() {
// DO SOMETHING TO PAY ON BLOCKCHAIN OR WHATEVER ON RETURN UPDATE STATUS // DO SOMETHING TO PAY ON BLOCKCHAIN OR WHATEVER ON RETURN UPDATE STATUS
d.Status = enum.PAID // TO REMOVE LATER IT'S A MOCK d.Status = enum.PAID // TO REMOVE LATER IT'S A MOCK
if d.Status == enum.PAID { if d.Status == enum.PAID {
for _, b := range d.Items { for _, b := range d.Items {
if !b.Item.IsPurchasable() { var priced *resources.PricedResource
bb, _ := json.Marshal(b.Item)
json.Unmarshal(bb, priced)
if !priced.IsPurchasable() {
continue continue
} }
accessor := purchase_resource.NewAccessor(request) accessor := purchase_resource.NewAccessor(request)
accessor.StoreOne(&purchase_resource.PurchaseResource{ accessor.StoreOne(&purchase_resource.PurchaseResource{
ResourceID: b.Item.GetID(), ResourceID: priced.GetID(),
ResourceType: b.Item.GetType(), ResourceType: priced.GetType(),
EndDate: b.Item.GetLocationEnd(), EndDate: priced.GetLocationEnd(),
}) })
} }
} }
@@ -162,7 +169,7 @@ func (d *PeerOrder) Pay(request *tools.APIRequest, response chan *PeerOrder, wg
func (d *PeerOrder) SumUpBill(request *tools.APIRequest) error { func (d *PeerOrder) SumUpBill(request *tools.APIRequest) error {
for _, b := range d.Items { for _, b := range d.Items {
tot, err := b.GetPrice(request) // missing something tot, err := b.GetPriceHT(request) // missing something
if err != nil { if err != nil {
return err return err
} }
@@ -174,14 +181,26 @@ func (d *PeerOrder) SumUpBill(request *tools.APIRequest) error {
type PeerItemOrder struct { type PeerItemOrder struct {
Quantity int `json:"quantity,omitempty" bson:"quantity,omitempty"` Quantity int `json:"quantity,omitempty" bson:"quantity,omitempty"`
Purchase *purchase_resource.PurchaseResource `json:"purchase,omitempty" bson:"purchase,omitempty"` Purchase *purchase_resource.PurchaseResource `json:"purchase,omitempty" bson:"purchase,omitempty"`
Item pricing.PricedItemITF `json:"item,omitempty" bson:"item,omitempty"` Item map[string]interface{} `json:"item,omitempty" bson:"item,omitempty"`
} }
func (d *PeerItemOrder) GetPrice(request *tools.APIRequest) (float64, error) { func (d *PeerItemOrder) GetPriceHT(request *tools.APIRequest) (float64, error) {
/////////// Temporary in order to allow GenerateOrder to complete while billing is still WIP
if d.Purchase == nil {
return 0, nil
}
///////////
var priced *resources.PricedResource
b, _ := json.Marshal(d.Item)
err := json.Unmarshal(b, priced)
if err != nil {
fmt.Println(err)
return 0, err
}
accessor := purchase_resource.NewAccessor(request) accessor := purchase_resource.NewAccessor(request)
search, code, _ := accessor.Search(&dbs.Filters{ search, code, _ := accessor.Search(&dbs.Filters{
And: map[string][]dbs.Filter{ And: map[string][]dbs.Filter{
"resource_id": {{Operator: dbs.EQUAL.String(), Value: d.Item.GetID()}}, "resource_id": {{Operator: dbs.EQUAL.String(), Value: priced.GetID()}},
}, },
}, "", d.Purchase.IsDraft) }, "", d.Purchase.IsDraft)
if code == 200 && len(search) > 0 { if code == 200 && len(search) > 0 {
@@ -191,7 +210,7 @@ func (d *PeerItemOrder) GetPrice(request *tools.APIRequest) (float64, error) {
} }
} }
} }
p, err := d.Item.GetPrice() p, err := priced.GetPriceHT()
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@@ -6,7 +6,6 @@ import (
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/common/enum" "cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/common/models" "cloud.o-forge.io/core/oc-lib/models/common/models"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
@@ -17,13 +16,13 @@ import (
*/ */
type Booking struct { type Booking struct {
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
PricedItem pricing.PricedItemITF `json:"priced_item,omitempty" bson:"priced_item,omitempty"` // We need to add the validate:"required" tag once the pricing feature is implemented, removed to avoid handling the error PricedItem map[string]interface{} `json:"priced_item,omitempty" bson:"priced_item,omitempty"` // We need to add the validate:"required" tag once the pricing feature is implemented, removed to avoid handling the error
ResumeMetrics map[string]map[string]models.MetricResume `json:"resume_metrics,omitempty" bson:"resume_metrics,omitempty"` ResumeMetrics map[string]map[string]models.MetricResume `json:"resume_metrics,omitempty" bson:"resume_metrics,omitempty"`
ExecutionMetrics map[string][]models.MetricsSnapshot `json:"metrics,omitempty" bson:"metrics,omitempty"` ExecutionMetrics map[string][]models.MetricsSnapshot `json:"metrics,omitempty" bson:"metrics,omitempty"`
ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
DestPeerID string `json:"dest_peer_id,omitempty"` // DestPeerID is the ID of the destination peer DestPeerID string `json:"dest_peer_id,omitempty" bson:"dest_peer_id,omitempty"` // DestPeerID is the ID of the destination peer
WorkflowID string `json:"workflow_id,omitempty" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow WorkflowID string `json:"workflow_id,omitempty" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow
ExecutionID string `json:"execution_id,omitempty" bson:"execution_id,omitempty" validate:"required"` ExecutionID string `json:"execution_id,omitempty" bson:"execution_id,omitempty" validate:"required"`
State enum.BookingStatus `json:"state,omitempty" bson:"state,omitempty" validate:"required"` // State is the state of the booking State enum.BookingStatus `json:"state,omitempty" bson:"state,omitempty" validate:"required"` // State is the state of the booking
@@ -119,7 +118,7 @@ func (d *Booking) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) // Create a new instance of the accessor return NewAccessor(request) // Create a new instance of the accessor
} }
func (d *Booking) VerifyAuth(request *tools.APIRequest) bool { func (d *Booking) VerifyAuth(callName string, request *tools.APIRequest) bool {
return true return true
} }

23
models/booking/enums.go Normal file
View File

@@ -0,0 +1,23 @@
package booking
type BookingMode int
const (
PLANNED BookingMode = iota // predictible
PREEMPTED // can be both predictible or unpredictible, first one asking for a quick exec, second on event, but we pay to preempt in any case.
WHEN_POSSIBLE // unpredictable, two mode of payment can be available on that case: fixed, or per USE
)
/*
Ok make a point there:
There is 3 notions about booking & payment :
Booking mode : WHEN is executed
Buying mode : Duration of payment
Pricing Mode : How Many time we pay
We can simplify Buying Mode and Pricing Mode, some Buying Mode implied limited pricing mode
Such as Rules. Just like PERMANENT BUYING can be paid only once.
Booking Mode on WHEN POSSIBLE make an exception, because we can't know when executed.
*/

View File

@@ -42,7 +42,7 @@ func TestBooking_GetAccessor(t *testing.T) {
} }
func TestBooking_VerifyAuth(t *testing.T) { func TestBooking_VerifyAuth(t *testing.T) {
assert.True(t, (&booking.Booking{}).VerifyAuth(nil)) assert.True(t, (&booking.Booking{}).VerifyAuth("get", nil))
} }
func TestBooking_StoreDraftDefault(t *testing.T) { func TestBooking_StoreDraftDefault(t *testing.T) {

View File

@@ -71,7 +71,7 @@ func (ao *CollaborativeArea) Clear(peerID string) {
ao.CollaborativeAreaRule.CreatedAt = time.Now().UTC() ao.CollaborativeAreaRule.CreatedAt = time.Now().UTC()
} }
func (ao *CollaborativeArea) VerifyAuth(request *tools.APIRequest) bool { func (ao *CollaborativeArea) VerifyAuth(callName string, request *tools.APIRequest) bool {
if (ao.AllowedPeersGroup != nil || config.GetConfig().Whitelist) && request != nil { if (ao.AllowedPeersGroup != nil || config.GetConfig().Whitelist) && request != nil {
if grps, ok := ao.AllowedPeersGroup[request.PeerID]; ok || config.GetConfig().Whitelist { if grps, ok := ao.AllowedPeersGroup[request.PeerID]; ok || config.GetConfig().Whitelist {
if slices.Contains(grps, "*") || (!ok && config.GetConfig().Whitelist) { if slices.Contains(grps, "*") || (!ok && config.GetConfig().Whitelist) {
@@ -84,7 +84,7 @@ func (ao *CollaborativeArea) VerifyAuth(request *tools.APIRequest) bool {
} }
} }
} }
return ao.AbstractObject.VerifyAuth(request) return ao.AbstractObject.VerifyAuth(callName, request)
} }
func (d *CollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor { func (d *CollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor {
@@ -97,7 +97,7 @@ func (d *CollaborativeArea) Trim() *CollaborativeArea {
func (d *CollaborativeArea) StoreDraftDefault() { func (d *CollaborativeArea) StoreDraftDefault() {
d.AllowedPeersGroup = map[string][]string{ d.AllowedPeersGroup = map[string][]string{
d.CreatorID: []string{"*"}, d.CreatorID: {"*"},
} }
d.IsDraft = false d.IsDraft = false
} }

View File

@@ -63,7 +63,12 @@ func (a *collaborativeAreaMongoAccessor) UpdateOne(set utils.DBObject, id string
// StoreOne stores a collaborative area in the database, it automatically share to peers if the workspace is shared // StoreOne stores a collaborative area in the database, it automatically share to peers if the workspace is shared
func (a *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { func (a *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
_, id := (&peer.Peer{}).IsMySelf() // get the local peer id, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
})) // get the local peer
if err != nil {
return data, 404, err
}
data.(*CollaborativeArea).Clear(id) // set the creator data.(*CollaborativeArea).Clear(id) // set the creator
// retrieve or proper peer // retrieve or proper peer
if data.(*CollaborativeArea).CollaborativeAreaRule != nil { if data.(*CollaborativeArea).CollaborativeAreaRule != nil {
@@ -271,7 +276,9 @@ func (a *collaborativeAreaMongoAccessor) contactPeer(shared *CollaborativeArea,
paccess := (&peer.Peer{}) paccess := (&peer.Peer{})
for k := range shared.AllowedPeersGroup { for k := range shared.AllowedPeersGroup {
if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: k}}).IsMySelf(); ok || (shared.IsSent && meth == tools.POST) || (!shared.IsSent && meth != tools.POST) { if ok, _ := utils.IsMySelf(k, (&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
})); ok || (shared.IsSent && meth == tools.POST) || (!shared.IsSent && meth != tools.POST) {
continue continue
} }
shared.IsSent = meth == tools.POST shared.IsSent = meth == tools.POST

View File

@@ -24,6 +24,6 @@ func (d *Rule) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) return NewAccessor(request)
} }
func (d *Rule) VerifyAuth(request *tools.APIRequest) bool { func (d *Rule) VerifyAuth(callName string, request *tools.APIRequest) bool {
return true return true
} }

View File

@@ -3,6 +3,7 @@ package pricing
import ( import (
"time" "time"
"cloud.o-forge.io/core/oc-lib/models/booking"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
@@ -11,6 +12,9 @@ type PricedItemITF interface {
GetType() tools.DataType GetType() tools.DataType
IsPurchasable() bool IsPurchasable() bool
IsBooked() bool IsBooked() bool
GetQuantity() int
AddQuantity(amount int)
GetBookingMode() booking.BookingMode
GetCreatorID() string GetCreatorID() string
SelectPricing() PricingProfileITF SelectPricing() PricingProfileITF
GetLocationStart() *time.Time GetLocationStart() *time.Time
@@ -18,5 +22,5 @@ type PricedItemITF interface {
SetLocationEnd(end time.Time) SetLocationEnd(end time.Time)
GetLocationEnd() *time.Time GetLocationEnd() *time.Time
GetExplicitDurationInS() float64 GetExplicitDurationInS() float64
GetPrice() (float64, error) GetPriceHT() (float64, error)
} }

View File

@@ -9,7 +9,7 @@ type PricingProfileITF interface {
IsPurchasable() bool IsPurchasable() bool
GetPurchase() BuyingStrategy GetPurchase() BuyingStrategy
GetOverrideStrategyValue() int GetOverrideStrategyValue() int
GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) GetPriceHT(quantity float64, val float64, start time.Time, end time.Time, variation []*PricingVariation, params ...string) (float64, error)
} }
type RefundType int type RefundType int
@@ -34,10 +34,37 @@ type AccessPricingProfile[T Strategy] struct { // only use for acces such as : D
RefundRatio int32 `json:"refund_ratio" bson:"refund_ratio" default:"0"` // RefundRatio is the refund ratio if missing RefundRatio int32 `json:"refund_ratio" bson:"refund_ratio" default:"0"` // RefundRatio is the refund ratio if missing
} }
func (a AccessPricingProfile[T]) IsBooked() bool {
return a.Pricing.BuyingStrategy == SUBSCRIPTION
}
func (a AccessPricingProfile[T]) IsPurchasable() bool {
return a.Pricing.BuyingStrategy == PERMANENT
}
func (a AccessPricingProfile[T]) GetPurchase() BuyingStrategy {
return a.Pricing.BuyingStrategy
}
func (a AccessPricingProfile[T]) GetPriceHT(quantity float64, val float64, start time.Time, end time.Time, variations []*PricingVariation, params ...string) (float64, error) {
return a.Pricing.GetPriceHT(quantity, val, start, &end, variations)
}
func (b *AccessPricingProfile[T]) GetOverrideStrategyValue() int { func (b *AccessPricingProfile[T]) GetOverrideStrategyValue() int {
return -1 return -1
} }
func GetDefaultPricingProfile() PricingProfileITF {
return &AccessPricingProfile[TimePricingStrategy]{
Pricing: PricingStrategy[TimePricingStrategy]{
Price: 0,
Currency: "EUR",
BuyingStrategy: PERMANENT,
TimePricingStrategy: ONCE,
},
}
}
type ExploitPrivilegeStrategy int type ExploitPrivilegeStrategy int
const ( const (

View File

@@ -93,6 +93,10 @@ func (t TimePricingStrategy) String() string {
return [...]string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}[t] return [...]string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}[t]
} }
func TimePricingStrategyListStr() []string {
return []string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}
}
func TimePricingStrategyList() []TimePricingStrategy { func TimePricingStrategyList() []TimePricingStrategy {
return []TimePricingStrategy{ONCE, PER_SECOND, PER_MINUTE, PER_HOUR, PER_DAY, PER_WEEK, PER_MONTH} return []TimePricingStrategy{ONCE, PER_SECOND, PER_MINUTE, PER_HOUR, PER_DAY, PER_WEEK, PER_MONTH}
} }
@@ -159,11 +163,37 @@ type PricingStrategy[T Strategy] struct {
OverrideStrategy T `json:"override_strategy" bson:"override_strategy" default:"-1"` // Modulation is the modulation of the pricing OverrideStrategy T `json:"override_strategy" bson:"override_strategy" default:"-1"` // Modulation is the modulation of the pricing
} }
func (p PricingStrategy[T]) GetPrice(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time) (float64, error) { func (p PricingStrategy[T]) GetPriceHT(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time, variations []*PricingVariation) (float64, error) {
if p.BuyingStrategy == SUBSCRIPTION { if p.BuyingStrategy == SUBSCRIPTION {
return BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end) price, err := BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end)
} else if p.BuyingStrategy == PERMANENT { if err != nil {
return 0, err
}
if variations != nil {
for _, v := range variations {
price = v.GetPriceHT(price)
}
return price, nil
}
return p.Price, nil return p.Price, nil
} else if p.BuyingStrategy == PERMANENT {
if variations != nil {
price := p.Price
for _, v := range variations {
price = v.GetPriceHT(price)
}
return price, nil
}
return p.Price, nil
}
if variations != nil {
price := p.Price
for _, v := range variations {
price = v.GetPriceHT(price)
}
return price, nil
} }
return p.Price * float64(amountOfData), nil return p.Price * float64(amountOfData), nil
} }
@@ -179,3 +209,18 @@ func (p PricingStrategy[T]) GetTimePricingStrategy() TimePricingStrategy {
func (p PricingStrategy[T]) GetOverrideStrategy() T { func (p PricingStrategy[T]) GetOverrideStrategy() T {
return p.OverrideStrategy return p.OverrideStrategy
} }
type PricingVariation struct {
Inflate bool `json:"inflate" bson:"price"` // Price is the Price of the pricing
Percentage float64 `json:"percent" bson:"percent"` // Currency is the currency of the pricing // Modulation is the modulation of the pricing
Priority int `json:"priority" bson:"priority"`
}
func (pv *PricingVariation) GetPriceHT(priceHT float64) float64 {
value := (priceHT * pv.Percentage) / 100
if pv.Inflate {
return priceHT + value
} else {
return priceHT - value
}
}

View File

@@ -100,7 +100,7 @@ func TestPricingStrategy_Getters(t *testing.T) {
assert.Equal(t, DummyStrategy(1), ps.GetOverrideStrategy()) assert.Equal(t, DummyStrategy(1), ps.GetOverrideStrategy())
} }
func TestPricingStrategy_GetPrice(t *testing.T) { func TestPricingStrategy_GetPriceHT(t *testing.T) {
start := time.Now() start := time.Now()
end := start.Add(1 * time.Hour) end := start.Add(1 * time.Hour)
@@ -111,19 +111,19 @@ func TestPricingStrategy_GetPrice(t *testing.T) {
TimePricingStrategy: pricing.PER_HOUR, TimePricingStrategy: pricing.PER_HOUR,
} }
p, err := ps.GetPrice(2, 3600, start, &end) p, err := ps.GetPriceHT(2, 3600, start, &end, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, p > 0) assert.True(t, p > 0)
// UNLIMITED case // UNLIMITED case
ps.BuyingStrategy = pricing.PERMANENT ps.BuyingStrategy = pricing.PERMANENT
p, err = ps.GetPrice(10, 0, start, &end) p, err = ps.GetPriceHT(10, 0, start, &end, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 5.0, p) assert.Equal(t, 5.0, p)
// PAY_PER_USE case // PAY_PER_USE case
//ps.BuyingStrategy = pricing.PAY_PER_USE //ps.BuyingStrategy = pricing.PAY_PER_USE
p, err = ps.GetPrice(3, 0, start, &end) p, err = ps.GetPriceHT(3, 0, start, &end, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 15.0, p) assert.Equal(t, 15.0, p)
} }

View File

@@ -29,6 +29,7 @@ var ModelsCatalog = map[string]func() utils.DBObject{
tools.COMPUTE_RESOURCE.String(): func() utils.DBObject { return &resource.ComputeResource{} }, tools.COMPUTE_RESOURCE.String(): func() utils.DBObject { return &resource.ComputeResource{} },
tools.STORAGE_RESOURCE.String(): func() utils.DBObject { return &resource.StorageResource{} }, tools.STORAGE_RESOURCE.String(): func() utils.DBObject { return &resource.StorageResource{} },
tools.PROCESSING_RESOURCE.String(): func() utils.DBObject { return &resource.ProcessingResource{} }, tools.PROCESSING_RESOURCE.String(): func() utils.DBObject { return &resource.ProcessingResource{} },
tools.NATIVE_TOOL.String(): func() utils.DBObject { return &resource.NativeTool{} },
tools.WORKFLOW.String(): func() utils.DBObject { return &w2.Workflow{} }, tools.WORKFLOW.String(): func() utils.DBObject { return &w2.Workflow{} },
tools.WORKFLOW_EXECUTION.String(): func() utils.DBObject { return &workflow_execution.WorkflowExecution{} }, tools.WORKFLOW_EXECUTION.String(): func() utils.DBObject { return &workflow_execution.WorkflowExecution{} },
tools.WORKSPACE.String(): func() utils.DBObject { return &w3.Workspace{} }, tools.WORKSPACE.String(): func() utils.DBObject { return &w3.Workspace{} },

View File

@@ -44,7 +44,7 @@ func (o *Order) Quantity() int {
return len(o.Purchases) + len(o.Purchases) return len(o.Purchases) + len(o.Purchases)
} }
func (d *Order) SetName() { func (d *Order) SetName(_ string) {
d.Name = d.UUID + "_order_" + "_" + time.Now().UTC().Format("2006-01-02T15:04:05") d.Name = d.UUID + "_order_" + "_" + time.Now().UTC().Format("2006-01-02T15:04:05")
} }

View File

@@ -2,20 +2,18 @@ package peer
import ( import (
"fmt" "fmt"
"strings"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
// now write a go enum for the state partner with self, blacklist, partner // now write a go enum for the state partner with self, blacklist, partner
type PeerState int type PeerState int
const ( const (
NONE PeerState = iota OFFLINE PeerState = iota
SELF ONLINE
PARTNER
BLACKLIST
) )
func (m PeerState) String() string { func (m PeerState) String() string {
@@ -26,18 +24,58 @@ func (m PeerState) EnumIndex() int {
return int(m) return int(m)
} }
type PeerRelation int
const (
NONE PeerRelation = iota
SELF
PARTNER
BLACKLIST
PENDING_PARTNER
)
var path = []string{"unknown", "self", "partner", "blacklist", "partner"}
func GetRelationPath(str string) int {
for i, p := range path {
if str == p {
return i
}
}
return -1
}
func (m PeerRelation) Path() string {
return path[m]
}
func (m PeerRelation) String() string {
return strings.ToUpper(path[m])
}
func (m PeerRelation) EnumIndex() int {
return int(m)
}
// Peer is a struct that represents a peer // Peer is a struct that represents a peer
type Peer struct { type Peer struct {
utils.AbstractObject utils.AbstractObject
Url string `json:"url" bson:"url" validate:"required"` // Url is the URL of the peer (base64url)
Verify bool `json:"verify" bson:"verify"`
PeerID string `json:"peer_id" bson:"peer_id" validate:"required"`
APIUrl string `json:"api_url" bson:"api_url" validate:"required"` // Url is the URL of the peer (base64url)
StreamAddress string `json:"stream_address" bson:"stream_address" validate:"required"` // Url is the URL of the peer (base64url)
NATSAddress string `json:"nats_address" bson:"nats_address" validate:"required"`
WalletAddress string `json:"wallet_address" bson:"wallet_address" validate:"required"` // WalletAddress is the wallet address of the peer WalletAddress string `json:"wallet_address" bson:"wallet_address" validate:"required"` // WalletAddress is the wallet address of the peer
PublicKey string `json:"public_key" bson:"public_key" validate:"required"` // PublicKey is the public key of the peer PublicKey string `json:"public_key" bson:"public_key" validate:"required"` // PublicKey is the public key of the peer
State PeerState `json:"state" bson:"state" default:"0"` State PeerState `json:"state" bson:"state" default:"0"`
Relation PeerRelation `json:"relation" bson:"state" default:"0"`
ServicesState map[string]int `json:"services_state,omitempty" bson:"services_state,omitempty"` ServicesState map[string]int `json:"services_state,omitempty" bson:"services_state,omitempty"`
FailedExecution []PeerExecution `json:"failed_execution" bson:"failed_execution"` // FailedExecution is the list of failed executions, to be retried FailedExecution []PeerExecution `json:"failed_execution" bson:"failed_execution"` // FailedExecution is the list of failed executions, to be retried
} }
func (ao *Peer) VerifyAuth(request *tools.APIRequest) bool { func (ao *Peer) VerifyAuth(callName string, request *tools.APIRequest) bool {
return true return true
} }
@@ -66,16 +104,6 @@ func (ao *Peer) RemoveExecution(exec PeerExecution) {
ao.FailedExecution = new ao.FailedExecution = new
} }
// IsMySelf checks if the peer is the local peer
func (p *Peer) IsMySelf() (bool, string) {
d, code, err := NewAccessor(nil).Search(nil, SELF.String(), p.IsDraft)
if code != 200 || err != nil || len(d) == 0 {
return false, ""
}
id := d[0].GetID()
return p.UUID == id, id
}
// LaunchPeerExecution launches an execution on a peer // LaunchPeerExecution launches an execution on a peer
func (p *Peer) LaunchPeerExecution(peerID string, dataID string, dt tools.DataType, method tools.METHOD, body interface{}, caller *tools.HTTPCaller) (map[string]interface{}, error) { func (p *Peer) LaunchPeerExecution(peerID string, dataID string, dt tools.DataType, method tools.METHOD, body interface{}, caller *tools.HTTPCaller) (map[string]interface{}, error) {
p.UUID = peerID p.UUID = peerID

View File

@@ -28,19 +28,19 @@ type PeerCache struct {
} }
// urlFormat formats the URL of the peer with the data type API function // urlFormat formats the URL of the peer with the data type API function
func (p *PeerCache) urlFormat(hostUrl string, dt tools.DataType) string { func urlFormat(hostUrl string, dt tools.DataType) string {
return hostUrl + "/" + strings.ReplaceAll(dt.API(), "oc-", "") return hostUrl + "/" + strings.ReplaceAll(dt.API(), "oc-", "")
} }
// checkPeerStatus checks the status of a peer // checkPeerStatus checks the status of a peer
func (p *PeerCache) checkPeerStatus(peerID string, appName string) (*Peer, bool) { func CheckPeerStatus(peerID string, appName string) (*Peer, bool) {
api := tools.API{} api := tools.API{}
access := NewShallowAccessor() access := NewShallowAccessor()
res, code, _ := access.LoadOne(peerID) // Load the peer from db res, code, _ := access.LoadOne(peerID) // Load the peer from db
if code != 200 { // no peer no party if code != 200 { // no peer no party
return nil, false return nil, false
} }
url := p.urlFormat(res.(*Peer).Url, tools.PEER) + "/status" // Format the URL url := urlFormat(res.(*Peer).APIUrl, tools.PEER) + "/status" // Format the URL
state, services := api.CheckRemotePeer(url) state, services := api.CheckRemotePeer(url)
res.(*Peer).ServicesState = services // Update the services states of the peer res.(*Peer).ServicesState = services // Update the services states of the peer
access.UpdateOne(res, peerID) // Update the peer in the db access.UpdateOne(res, peerID) // Update the peer in the db
@@ -61,11 +61,11 @@ func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
url := "" url := ""
// Check the status of the peer // Check the status of the peer
if mypeer, ok := p.checkPeerStatus(peerID, dt.API()); !ok && mypeer != nil { if mypeer, ok := CheckPeerStatus(peerID, dt.API()); !ok && mypeer != nil {
// If the peer is not reachable, add the execution to the failed executions list // If the peer is not reachable, add the execution to the failed executions list
pexec := &PeerExecution{ pexec := &PeerExecution{
Method: method.String(), Method: method.String(),
Url: p.urlFormat((mypeer.Url), dt) + path, // the url is constitued of : host URL + resource path + action path (ex : mypeer.com/datacenter/resourcetype/path/to/action) Url: urlFormat((mypeer.APIUrl), dt) + path, // the url is constitued of : host URL + resource path + action path (ex : mypeer.com/datacenter/resourcetype/path/to/action)
Body: body, Body: body,
DataType: dt.EnumIndex(), DataType: dt.EnumIndex(),
DataID: dataID, DataID: dataID,
@@ -78,7 +78,7 @@ func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
return map[string]interface{}{}, errors.New("peer " + peerID + " not found") return map[string]interface{}{}, errors.New("peer " + peerID + " not found")
} }
// If the peer is reachable, launch the execution // If the peer is reachable, launch the execution
url = p.urlFormat((mypeer.Url), dt) + path // Format the URL url = urlFormat((mypeer.APIUrl), dt) + path // Format the URL
tmp := mypeer.FailedExecution // Get the failed executions list tmp := mypeer.FailedExecution // Get the failed executions list
mypeer.FailedExecution = []PeerExecution{} // Reset the failed executions list mypeer.FailedExecution = []PeerExecution{} // Reset the failed executions list
NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db

View File

@@ -80,19 +80,27 @@ func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string, isDraf
} }
func (a *peerMongoAccessor) GetDefaultFilter(search string) *dbs.Filters { func (a *peerMongoAccessor) GetDefaultFilter(search string) *dbs.Filters {
if i, err := strconv.Atoi(search); err == nil { if i, err := strconv.Atoi(search); err == nil {
m := map[string][]dbs.Filter{ // search by name if no filters are provided
"relation": {{Operator: dbs.EQUAL.String(), Value: i}},
}
if i == PARTNER.EnumIndex() {
m["verify"] = []dbs.Filter{{Operator: dbs.EQUAL.String(), Value: false}}
}
return &dbs.Filters{ return &dbs.Filters{
Or: map[string][]dbs.Filter{ // search by name if no filters are provided Or: m,
"state": {{Operator: dbs.EQUAL.String(), Value: i}},
},
} }
} else { } else {
if search == "*" { if search == "*" {
search = "" search = ""
} }
return &dbs.Filters{ return &dbs.Filters{
And: map[string][]dbs.Filter{ // search by name if no filters are provided
"state": {{Operator: dbs.EQUAL.String(), Value: ONLINE.EnumIndex()}},
},
Or: map[string][]dbs.Filter{ // search by name if no filters are provided Or: map[string][]dbs.Filter{ // search by name if no filters are provided
"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}}, "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
"url": {{Operator: dbs.LIKE.String(), Value: search}}, "url": {{Operator: dbs.LIKE.String(), Value: search}},
"peer_id": {{Operator: dbs.LIKE.String(), Value: search}},
}, },
} }
} }

View File

@@ -48,10 +48,12 @@ func (m *MockAccessor) Search(filters *dbs.Filters, search string, isDraft bool)
func newTestPeer() *peer.Peer { func newTestPeer() *peer.Peer {
return &peer.Peer{ return &peer.Peer{
Url: "http://localhost", NATSAddress: "",
StreamAddress: "127.0.0.1",
APIUrl: "http://localhost",
WalletAddress: "0x123", WalletAddress: "0x123",
PublicKey: "pubkey", PublicKey: "pubkey",
State: peer.SELF, Relation: peer.SELF,
} }
} }

View File

@@ -10,6 +10,7 @@ import (
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
) )
/* /*
@@ -30,15 +31,18 @@ func (r *ComputeResource) GetType() string {
return tools.COMPUTE_RESOURCE.String() return tools.COMPUTE_RESOURCE.String()
} }
func (abs *ComputeResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { func (abs *ComputeResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) {
if t != tools.COMPUTE_RESOURCE { if t != tools.COMPUTE_RESOURCE {
return nil return nil, errors.New("not the proper type expected : cannot convert to priced resource : have " + t.String() + " wait Compute")
}
p, err := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, selectedInstance, selectedPartnership, selectedBuyingStrategy, selectedStrategy, selectedBookingModeIndex, request)
if err != nil {
return nil, err
} }
p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
priced := p.(*PricedResource) priced := p.(*PricedResource)
return &PricedComputeResource{ return &PricedComputeResource{
PricedResource: *priced, PricedResource: *priced,
} }, nil
} }
type ComputeNode struct { type ComputeNode struct {
@@ -60,6 +64,17 @@ type ComputeResourceInstance struct {
Nodes []*ComputeNode `json:"nodes,omitempty" bson:"nodes,omitempty"` Nodes []*ComputeNode `json:"nodes,omitempty" bson:"nodes,omitempty"`
} }
func NewComputeResourceInstance(name string, peerID string) ResourceInstanceITF {
return &ComputeResourceInstance{
ResourceInstance: ResourceInstance[*ComputeResourcePartnership]{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: name,
},
},
}
}
type ComputeResourcePartnership struct { type ComputeResourcePartnership struct {
ResourcePartnerShip[*ComputeResourcePricingProfile] ResourcePartnerShip[*ComputeResourcePricingProfile]
MinGaranteedCPUsCores map[string]float64 `json:"garanteed_cpus,omitempty" bson:"garanteed_cpus,omitempty"` MinGaranteedCPUsCores map[string]float64 `json:"garanteed_cpus,omitempty" bson:"garanteed_cpus,omitempty"`
@@ -100,7 +115,7 @@ func (p *ComputeResourcePricingProfile) GetOverrideStrategyValue() int {
// NOT A PROPER QUANTITY // NOT A PROPER QUANTITY
// amountOfData is the number of CPUs, GPUs or RAM dependings on the params // amountOfData is the number of CPUs, GPUs or RAM dependings on the params
func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, params ...string) (float64, error) { func (p *ComputeResourcePricingProfile) GetPriceHT(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, variation []*pricing.PricingVariation, params ...string) (float64, error) {
if len(params) < 1 { if len(params) < 1 {
return 0, errors.New("params must be set") return 0, errors.New("params must be set")
} }
@@ -110,7 +125,7 @@ func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitD
if _, ok := p.CPUsPrices[model]; ok { if _, ok := p.CPUsPrices[model]; ok {
p.Pricing.Price = p.CPUsPrices[model] p.Pricing.Price = p.CPUsPrices[model]
} }
r, err := p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end) r, err := p.Pricing.GetPriceHT(amountOfData, explicitDuration, start, &end, variation)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -121,7 +136,7 @@ func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitD
if _, ok := p.GPUsPrices[model]; ok { if _, ok := p.GPUsPrices[model]; ok {
p.Pricing.Price = p.GPUsPrices[model] p.Pricing.Price = p.GPUsPrices[model]
} }
r, err := p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end) r, err := p.Pricing.GetPriceHT(amountOfData, explicitDuration, start, &end, variation)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -131,7 +146,7 @@ func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitD
if p.RAMPrice >= 0 { if p.RAMPrice >= 0 {
p.Pricing.Price = p.RAMPrice p.Pricing.Price = p.RAMPrice
} }
r, err := p.Pricing.GetPrice(float64(amountOfData), explicitDuration, start, &end) r, err := p.Pricing.GetPriceHT(float64(amountOfData), explicitDuration, start, &end, variation)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -152,14 +167,17 @@ func (r *PricedComputeResource) GetType() tools.DataType {
return tools.COMPUTE_RESOURCE return tools.COMPUTE_RESOURCE
} }
func (r *PricedComputeResource) GetPrice() (float64, error) { func (r *PricedComputeResource) GetPriceHT() (float64, error) {
now := time.Now() if r.BookingConfiguration == nil {
if r.UsageStart == nil { r.BookingConfiguration = &BookingConfiguration{}
r.UsageStart = &now
} }
if r.UsageEnd == nil { now := time.Now()
add := r.UsageStart.Add(time.Duration(1 * time.Hour)) if r.BookingConfiguration.UsageStart == nil {
r.UsageEnd = &add r.BookingConfiguration.UsageStart = &now
}
if r.BookingConfiguration.UsageEnd == nil {
add := r.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour))
r.BookingConfiguration.UsageEnd = &add
} }
if r.SelectedPricing == nil { if r.SelectedPricing == nil {
return 0, errors.New("pricing profile must be set on Priced Compute" + r.ResourceID) return 0, errors.New("pricing profile must be set on Priced Compute" + r.ResourceID)
@@ -168,14 +186,17 @@ func (r *PricedComputeResource) GetPrice() (float64, error) {
price := float64(0) price := float64(0)
for _, l := range []map[string]float64{r.CPUsLocated, r.GPUsLocated} { for _, l := range []map[string]float64{r.CPUsLocated, r.GPUsLocated} {
for model, amountOfData := range l { for model, amountOfData := range l {
cpus, err := pricing.GetPrice(float64(amountOfData), r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "cpus", model) cpus, err := pricing.GetPriceHT(float64(amountOfData),
r.BookingConfiguration.ExplicitBookingDurationS, *r.BookingConfiguration.UsageStart,
*r.BookingConfiguration.UsageEnd, r.Variations, "cpus", model)
if err != nil { if err != nil {
return 0, err return 0, err
} }
price += cpus price += cpus
} }
} }
ram, err := pricing.GetPrice(r.RAMLocated, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "ram") ram, err := pricing.GetPriceHT(r.RAMLocated, r.BookingConfiguration.ExplicitBookingDurationS,
*r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations, "ram")
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@@ -9,6 +9,7 @@ import (
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
) )
/* /*
@@ -37,15 +38,18 @@ func (r *DataResource) GetType() string {
return tools.DATA_RESOURCE.String() return tools.DATA_RESOURCE.String()
} }
func (abs *DataResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { func (abs *DataResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) {
if t != tools.DATA_RESOURCE { if t != tools.DATA_RESOURCE {
return nil return nil, errors.New("not the proper type expected : cannot convert to priced resource : have " + t.String() + " wait Data")
}
p, err := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, selectedInstance, selectedPartnership, selectedBuyingStrategy, selectedStrategy, selectedBookingModeIndex, request)
if err != nil {
return nil, err
} }
p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
priced := p.(*PricedResource) priced := p.(*PricedResource)
return &PricedDataResource{ return &PricedDataResource{
PricedResource: *priced, PricedResource: *priced,
} }, nil
} }
type DataInstance struct { type DataInstance struct {
@@ -53,6 +57,17 @@ type DataInstance struct {
Source string `json:"source,omitempty" bson:"source,omitempty"` // Source is the source of the data Source string `json:"source,omitempty" bson:"source,omitempty"` // Source is the source of the data
} }
func NewDataInstance(name string, peerID string) ResourceInstanceITF {
return &DataInstance{
ResourceInstance: ResourceInstance[*DataResourcePartnership]{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: name,
},
},
}
}
func (ri *DataInstance) StoreDraftDefault() { func (ri *DataInstance) StoreDraftDefault() {
found := false found := false
for _, p := range ri.ResourceInstance.Env { for _, p := range ri.ResourceInstance.Env {
@@ -89,7 +104,9 @@ const (
) )
func (t DataResourcePricingStrategy) String() string { func (t DataResourcePricingStrategy) String() string {
return [...]string{"PER DOWNLOAD", "PER TB DOWNLOADED", "PER GB DOWNLOADED", "PER MB DOWNLOADED", "PER KB DOWNLOADED"}[t] l := pricing.TimePricingStrategyListStr()
l = append(l, []string{"PER DOWNLOAD", "PER TB DOWNLOADED", "PER GB DOWNLOADED", "PER MB DOWNLOADED", "PER KB DOWNLOADED"}...)
return l[t]
} }
func DataResourcePricingStrategyList() []DataResourcePricingStrategy { func DataResourcePricingStrategyList() []DataResourcePricingStrategy {
@@ -101,7 +118,9 @@ func ToDataResourcePricingStrategy(i int) DataResourcePricingStrategy {
} }
func (t DataResourcePricingStrategy) GetStrategy() string { func (t DataResourcePricingStrategy) GetStrategy() string {
return [...]string{"PER_DOWNLOAD", "PER_GB", "PER_MB", "PER_KB"}[t] l := pricing.TimePricingStrategyListStr()
l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
return l[t]
} }
func (t DataResourcePricingStrategy) GetStrategyValue() int { func (t DataResourcePricingStrategy) GetStrategyValue() int {
@@ -132,14 +151,6 @@ func (p *DataResourcePricingProfile) GetOverrideStrategyValue() int {
return p.Pricing.OverrideStrategy.GetStrategyValue() return p.Pricing.OverrideStrategy.GetStrategyValue()
} }
func (p *DataResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, params ...string) (float64, error) {
return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
}
func (p *DataResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
return p.Pricing.BuyingStrategy
}
func (p *DataResourcePricingProfile) IsPurchasable() bool { func (p *DataResourcePricingProfile) IsPurchasable() bool {
return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
} }
@@ -158,15 +169,18 @@ func (r *PricedDataResource) GetType() tools.DataType {
return tools.DATA_RESOURCE return tools.DATA_RESOURCE
} }
func (r *PricedDataResource) GetPrice() (float64, error) { func (r *PricedDataResource) GetPriceHT() (float64, error) {
fmt.Println("GetPrice", r.UsageStart, r.UsageEnd) if r.BookingConfiguration == nil {
now := time.Now() r.BookingConfiguration = &BookingConfiguration{}
if r.UsageStart == nil {
r.UsageStart = &now
} }
if r.UsageEnd == nil { fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd)
add := r.UsageStart.Add(time.Duration(1 * time.Hour)) now := time.Now()
r.UsageEnd = &add if r.BookingConfiguration.UsageStart == nil {
r.BookingConfiguration.UsageStart = &now
}
if r.BookingConfiguration.UsageEnd == nil {
add := r.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour))
r.BookingConfiguration.UsageEnd = &add
} }
if r.SelectedPricing == nil { if r.SelectedPricing == nil {
return 0, errors.New("pricing profile must be set on Priced Data" + r.ResourceID) return 0, errors.New("pricing profile must be set on Priced Data" + r.ResourceID)
@@ -180,5 +194,7 @@ func (r *PricedDataResource) GetPrice() (float64, error) {
return 0, err return 0, err
} }
} }
return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd)
return pricing.GetPriceHT(amountOfData, r.BookingConfiguration.ExplicitBookingDurationS,
*r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations)
} }

View File

@@ -1,6 +1,8 @@
package resources package resources
import ( import (
"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/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
@@ -9,11 +11,15 @@ import (
type ResourceInterface interface { type ResourceInterface interface {
utils.DBObject utils.DBObject
Trim() Trim()
ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF FilterPeer(peerID string) *dbs.Filters
GetBookingModes() map[booking.BookingMode]*pricing.PricingVariation
ConvertToPricedResource(t tools.DataType, a *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, b *int, request *tools.APIRequest) (pricing.PricedItemITF, error)
GetType() string GetType() string
GetSelectedInstance() ResourceInstanceITF GetSelectedInstance(selected *int) ResourceInstanceITF
ClearEnv() utils.DBObject ClearEnv() utils.DBObject
SetAllowedInstances(request *tools.APIRequest) SetAllowedInstances(request *tools.APIRequest)
AddInstances(instance ResourceInstanceITF)
RefineResourceByPartnership(peerID string) ResourceInterface
} }
type ResourceInstanceITF interface { type ResourceInstanceITF interface {
@@ -22,17 +28,17 @@ type ResourceInstanceITF interface {
GetName() string GetName() string
StoreDraftDefault() StoreDraftDefault()
ClearEnv() ClearEnv()
GetProfile() pricing.PricingProfileITF GetProfile(peerID string, partnershipIndex *int, buying *int, strategy *int) pricing.PricingProfileITF
GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string)
ClearPeerGroups() ClearPeerGroups()
GetSelectedPartnership(peerID string, groups []string) ResourcePartnerITF RefineResourceByPartnership(peerID string) (ResourceInstanceITF, bool)
GetPartnerships(peerID string, groups []string) []ResourcePartnerITF
} }
type ResourcePartnerITF interface { type ResourcePartnerITF interface {
RefineResourceByPartnership(peerID string) (ResourcePartnerITF, bool)
GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
GetPeerGroups() map[string][]string GetPeerGroups() map[string][]string
ClearPeerGroups() ClearPeerGroups()
GetProfile(buying int, strategy int) pricing.PricingProfileITF GetProfile(buying *int, strategy *int) pricing.PricingProfileITF
} }

View File

@@ -11,12 +11,14 @@ type ResourceSet struct {
Processings []string `bson:"processings,omitempty" json:"processings,omitempty"` Processings []string `bson:"processings,omitempty" json:"processings,omitempty"`
Computes []string `bson:"computes,omitempty" json:"computes,omitempty"` Computes []string `bson:"computes,omitempty" json:"computes,omitempty"`
Workflows []string `bson:"workflows,omitempty" json:"workflows,omitempty"` Workflows []string `bson:"workflows,omitempty" json:"workflows,omitempty"`
NativeTool []string `bson:"native,omitempty" json:"native,omitempty"`
DataResources []*DataResource `bson:"-" json:"data_resources,omitempty"` DataResources []*DataResource `bson:"-" json:"data_resources,omitempty"`
StorageResources []*StorageResource `bson:"-" json:"storage_resources,omitempty"` StorageResources []*StorageResource `bson:"-" json:"storage_resources,omitempty"`
ProcessingResources []*ProcessingResource `bson:"-" json:"processing_resources,omitempty"` ProcessingResources []*ProcessingResource `bson:"-" json:"processing_resources,omitempty"`
ComputeResources []*ComputeResource `bson:"-" json:"compute_resources,omitempty"` ComputeResources []*ComputeResource `bson:"-" json:"compute_resources,omitempty"`
WorkflowResources []*WorkflowResource `bson:"-" json:"workflow_resources,omitempty"` WorkflowResources []*WorkflowResource `bson:"-" json:"workflow_resources,omitempty"`
NativeTools []*NativeTool `bson:"-" json:"native_tools,omitempty"`
} }
func (r *ResourceSet) Clear() { func (r *ResourceSet) Clear() {
@@ -62,4 +64,5 @@ type ItemResource struct {
Storage *StorageResource `bson:"storage,omitempty" json:"storage,omitempty"` Storage *StorageResource `bson:"storage,omitempty" json:"storage,omitempty"`
Compute *ComputeResource `bson:"compute,omitempty" json:"compute,omitempty"` Compute *ComputeResource `bson:"compute,omitempty" json:"compute,omitempty"`
Workflow *WorkflowResource `bson:"workflow,omitempty" json:"workflow,omitempty"` Workflow *WorkflowResource `bson:"workflow,omitempty" json:"workflow,omitempty"`
NativeTool *NativeTool `bson:"native_tools,omitempty" json:"native_tools,omitempty"`
} }

View File

@@ -0,0 +1,77 @@
package resources
import (
"encoding/json"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/resources/native_tools"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
)
/*
* NativeT ools is a struct that represents Native Functionnality of OPENCLOUD
*/
type NativeTool struct {
AbstractResource
Kind int `json:"kind" bson:"kind" validate:"required"`
Params map[string]interface{}
}
func (d *NativeTool) SetName(name string) {
d.Name = name
}
func (d *NativeTool) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor[*NativeTool](tools.NATIVE_TOOL, request, func() utils.DBObject { return &NativeTool{} })
}
func (r *NativeTool) AddInstances(instance ResourceInstanceITF) {
}
func (r *NativeTool) GetType() string {
return tools.NATIVE_TOOL.String()
}
func (d *NativeTool) ClearEnv() utils.DBObject {
return d
}
func (d *NativeTool) Trim() {
/* EMPTY */
}
func (w *NativeTool) SetAllowedInstances(request *tools.APIRequest) {
/* EMPTY */
}
func (w *NativeTool) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) {
return &PricedResource{
Name: w.Name,
Logo: w.Logo,
ResourceID: w.UUID,
ResourceType: t,
Quantity: 1,
CreatorID: w.CreatorID,
}, nil
}
func (abs *NativeTool) RefineResourceByPartnership(peerID string) ResourceInterface {
return abs
}
func InitNative() {
for _, kind := range []native_tools.NativeToolsEnum{native_tools.WORKFLOW_EVENT} {
newNative := &NativeTool{}
access := newNative.GetAccessor(&tools.APIRequest{Admin: true})
l, _, err := access.Search(nil, kind.String(), false)
if err != nil || len(l) == 0 {
newNative.Name = kind.String()
newNative.Kind = int(kind)
b, _ := json.Marshal(kind.Params())
var m map[string]interface{}
json.Unmarshal(b, &m)
newNative.Params = m
access.StoreOne(newNative)
}
}
}

View File

@@ -0,0 +1,23 @@
package native_tools
type NativeToolsEnum int
const (
WORKFLOW_EVENT NativeToolsEnum = iota
)
var Params = [...]interface{}{
WorkflowEventParams{},
}
var Str = [...]string{
"WORKFLOW_EVENT",
}
func (d NativeToolsEnum) Params() interface{} {
return Str[d]
}
func (d NativeToolsEnum) String() string {
return Str[d]
}

View File

@@ -0,0 +1,19 @@
package native_tools
import (
"cloud.o-forge.io/core/oc-lib/models/booking"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
)
/*
* Workflow Event is a struct that represents a native functiunality.
*/
type WorkflowEventParams struct {
WorkflowResourceID string `json:"workflow_execution_id" bson:"workflow_execution_id" validate:"required"`
BookingMode *booking.BookingMode `json:"booking_mode" bson:"booking_mode"`
}
func (wep *WorkflowEventParams) GetBuyingStrategy() pricing.BillingStrategy {
return pricing.BILL_ONCE
}

View File

@@ -5,23 +5,39 @@ import (
"fmt" "fmt"
"time" "time"
"cloud.o-forge.io/core/oc-lib/models/booking"
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type BookingConfiguration struct {
ExplicitBookingDurationS float64 `json:"explicit_location_duration_s,omitempty" bson:"explicit_location_duration_s,omitempty"`
UsageStart *time.Time `json:"start,omitempty" bson:"start,omitempty"`
UsageEnd *time.Time `json:"end,omitempty" bson:"end,omitempty"`
Mode booking.BookingMode `json:"mode,omitempty" bson:"mode,omitempty"`
}
type PricedResource struct { type PricedResource struct {
Name string `json:"name,omitempty" bson:"name,omitempty"` Name string `json:"name,omitempty" bson:"name,omitempty"`
Logo string `json:"logo,omitempty" bson:"logo,omitempty"` Logo string `json:"logo,omitempty" bson:"logo,omitempty"`
InstancesRefs map[string]string `json:"instances_refs,omitempty" bson:"instances_refs,omitempty"` InstancesRefs map[string]string `json:"instances_refs,omitempty" bson:"instances_refs,omitempty"`
SelectedPricing pricing.PricingProfileITF `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"` SelectedPricing pricing.PricingProfileITF `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"`
ExplicitBookingDurationS float64 `json:"explicit_location_duration_s,omitempty" bson:"explicit_location_duration_s,omitempty"` Quantity int `json:"quantity,omitempty" bson:"quantity,omitempty"`
UsageStart *time.Time `json:"start,omitempty" bson:"start,omitempty"` BookingConfiguration *BookingConfiguration `json:"booking_configuration,omitempty" bson:"booking_configuration,omitempty"`
UsageEnd *time.Time `json:"end,omitempty" bson:"end,omitempty"` Variations []*pricing.PricingVariation `json:"pricing_variations" bson:"pricing_variations"`
CreatorID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"` CreatorID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
ResourceID string `json:"resource_id,omitempty" bson:"resource_id,omitempty"` ResourceID string `json:"resource_id,omitempty" bson:"resource_id,omitempty"`
ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty"` ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty"`
} }
func (abs *PricedResource) GetQuantity() int {
return abs.Quantity
}
func (abs *PricedResource) AddQuantity(amount int) {
abs.Quantity += amount
}
func (abs *PricedResource) SelectPricing() pricing.PricingProfileITF { func (abs *PricedResource) SelectPricing() pricing.PricingProfileITF {
return abs.SelectedPricing return abs.SelectedPricing
} }
@@ -46,6 +62,7 @@ func (abs *PricedResource) IsPurchasable() bool {
} }
func (abs *PricedResource) IsBooked() bool { func (abs *PricedResource) IsBooked() bool {
return true // For dev purposes, prevent that DB objects that don't have a Pricing are considered as not booked
if abs.SelectedPricing == nil { if abs.SelectedPricing == nil {
return false return false
} }
@@ -53,48 +70,73 @@ func (abs *PricedResource) IsBooked() bool {
} }
func (abs *PricedResource) GetLocationEnd() *time.Time { func (abs *PricedResource) GetLocationEnd() *time.Time {
return abs.UsageEnd if abs.BookingConfiguration == nil {
return nil
}
return abs.BookingConfiguration.UsageEnd
} }
func (abs *PricedResource) GetLocationStart() *time.Time { func (abs *PricedResource) GetLocationStart() *time.Time {
return abs.UsageStart if abs.BookingConfiguration == nil {
return nil
}
return abs.BookingConfiguration.UsageStart
} }
func (abs *PricedResource) SetLocationStart(start time.Time) { func (abs *PricedResource) SetLocationStart(start time.Time) {
abs.UsageStart = &start if abs.BookingConfiguration == nil {
abs.BookingConfiguration = &BookingConfiguration{}
}
abs.BookingConfiguration.UsageStart = &start
} }
func (abs *PricedResource) SetLocationEnd(end time.Time) { func (abs *PricedResource) SetLocationEnd(end time.Time) {
abs.UsageEnd = &end if abs.BookingConfiguration == nil {
abs.BookingConfiguration = &BookingConfiguration{}
}
abs.BookingConfiguration.UsageEnd = &end
}
func (abs *PricedResource) GetBookingMode() booking.BookingMode {
if abs.BookingConfiguration == nil {
return booking.WHEN_POSSIBLE
}
return abs.BookingConfiguration.Mode
} }
func (abs *PricedResource) GetExplicitDurationInS() float64 { func (abs *PricedResource) GetExplicitDurationInS() float64 {
if abs.ExplicitBookingDurationS == 0 { if abs.BookingConfiguration == nil {
if abs.UsageEnd == nil && abs.UsageStart == nil { abs.BookingConfiguration = &BookingConfiguration{}
}
if abs.BookingConfiguration.ExplicitBookingDurationS == 0 {
if abs.BookingConfiguration.UsageEnd == nil && abs.BookingConfiguration.UsageStart == nil {
return time.Duration(1 * time.Hour).Seconds() return time.Duration(1 * time.Hour).Seconds()
} }
if abs.UsageEnd == nil { if abs.BookingConfiguration.UsageEnd == nil {
add := abs.UsageStart.Add(time.Duration(1 * time.Hour)) add := abs.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour))
abs.UsageEnd = &add abs.BookingConfiguration.UsageEnd = &add
} }
return abs.UsageEnd.Sub(*abs.UsageStart).Seconds() return abs.BookingConfiguration.UsageEnd.Sub(*abs.BookingConfiguration.UsageStart).Seconds()
} }
return abs.ExplicitBookingDurationS return abs.BookingConfiguration.ExplicitBookingDurationS
} }
func (r *PricedResource) GetPrice() (float64, error) { func (r *PricedResource) GetPriceHT() (float64, error) {
fmt.Println("GetPrice", r.UsageStart, r.UsageEnd)
now := time.Now() now := time.Now()
if r.UsageStart == nil { if r.BookingConfiguration == nil {
r.UsageStart = &now r.BookingConfiguration = &BookingConfiguration{}
} }
if r.UsageEnd == nil { fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd)
add := r.UsageStart.Add(time.Duration(1 * time.Hour)) if r.BookingConfiguration.UsageStart == nil {
r.UsageEnd = &add r.BookingConfiguration.UsageStart = &now
}
if r.BookingConfiguration.UsageEnd == nil {
add := r.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour))
r.BookingConfiguration.UsageEnd = &add
} }
if r.SelectedPricing == nil { if r.SelectedPricing == nil {
return 0, errors.New("pricing profile must be set on Priced Resource " + r.ResourceID) return 0, errors.New("pricing profile must be set on Priced Resource " + r.ResourceID)
} }
pricing := r.SelectedPricing pricing := r.SelectedPricing
return pricing.GetPrice(1, 0, *r.UsageStart, *r.UsageEnd) return pricing.GetPriceHT(1, 0, *r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations)
} }

View File

@@ -8,6 +8,7 @@ import (
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
) )
type ProcessingUsage struct { type ProcessingUsage struct {
@@ -26,6 +27,7 @@ type ProcessingUsage struct {
*/ */
type ProcessingResource struct { type ProcessingResource struct {
AbstractInstanciatedResource[*ProcessingInstance] AbstractInstanciatedResource[*ProcessingInstance]
IsEvent bool `json:"is_event,omitempty" bson:"is_event,omitempty"`
Infrastructure enum.InfrastructureType `json:"infrastructure" bson:"infrastructure" default:"-1"` // Infrastructure is the infrastructure Infrastructure enum.InfrastructureType `json:"infrastructure" bson:"infrastructure" default:"-1"` // Infrastructure is the infrastructure
IsService bool `json:"is_service,omitempty" bson:"is_service,omitempty"` // IsService is a flag that indicates if the processing is a service IsService bool `json:"is_service,omitempty" bson:"is_service,omitempty"` // IsService is a flag that indicates if the processing is a service
Usage *ProcessingUsage `bson:"usage,omitempty" json:"usage,omitempty"` // Usage is the usage of the processing Usage *ProcessingUsage `bson:"usage,omitempty" json:"usage,omitempty"` // Usage is the usage of the processing
@@ -47,6 +49,21 @@ type ProcessingInstance struct {
Access *ProcessingResourceAccess `json:"access,omitempty" bson:"access,omitempty"` // Access is the access Access *ProcessingResourceAccess `json:"access,omitempty" bson:"access,omitempty"` // Access is the access
} }
func NewProcessingInstance(name string, peerID string) ResourceInstanceITF {
return &ProcessingInstance{
ResourceInstance: ResourceInstance[*ResourcePartnerShip[*ProcessingResourcePricingProfile]]{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: name,
},
},
}
}
type ProcessingResourcePartnership struct {
ResourcePartnerShip[*ProcessingResourcePricingProfile]
}
type PricedProcessingResource struct { type PricedProcessingResource struct {
PricedResource PricedResource
IsService bool IsService bool
@@ -57,16 +74,19 @@ func (r *PricedProcessingResource) GetType() tools.DataType {
} }
func (a *PricedProcessingResource) GetExplicitDurationInS() float64 { func (a *PricedProcessingResource) GetExplicitDurationInS() float64 {
if a.ExplicitBookingDurationS == 0 { if a.BookingConfiguration == nil {
if a.IsService || a.UsageStart == nil { a.BookingConfiguration = &BookingConfiguration{}
}
if a.BookingConfiguration.ExplicitBookingDurationS == 0 {
if a.IsService || a.BookingConfiguration.UsageStart == nil {
if a.IsService { if a.IsService {
return -1 return -1
} }
return time.Duration(1 * time.Hour).Seconds() return time.Duration(1 * time.Hour).Seconds()
} }
return a.UsageEnd.Sub(*a.UsageStart).Seconds() return a.BookingConfiguration.UsageEnd.Sub(*a.BookingConfiguration.UsageStart).Seconds()
} }
return a.ExplicitBookingDurationS return a.BookingConfiguration.ExplicitBookingDurationS
} }
func (d *ProcessingResource) GetAccessor(request *tools.APIRequest) utils.Accessor { func (d *ProcessingResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
@@ -84,11 +104,3 @@ func (p *ProcessingResourcePricingProfile) IsPurchasable() bool {
func (p *ProcessingResourcePricingProfile) IsBooked() bool { func (p *ProcessingResourcePricingProfile) IsBooked() bool {
return p.Pricing.BuyingStrategy != pricing.PERMANENT return p.Pricing.BuyingStrategy != pricing.PERMANENT
} }
func (p *ProcessingResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
return p.Pricing.BuyingStrategy
}
func (p *ProcessingResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) {
return p.Pricing.GetPrice(amountOfData, val, start, &end)
}

View File

@@ -3,15 +3,14 @@ package purchase_resource
import ( import (
"time" "time"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type PurchaseResource struct { type PurchaseResource struct {
utils.AbstractObject utils.AbstractObject
DestPeerID string DestPeerID string `json:"dest_peer_id" bson:"dest_peer_id"`
PricedItem pricing.PricedItemITF `json:"priced_item,omitempty" bson:"priced_item,omitempty" validate:"required"` PricedItem map[string]interface{} `json:"priced_item,omitempty" bson:"priced_item,omitempty" validate:"required"`
ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
EndDate *time.Time `json:"end_buying_date,omitempty" bson:"end_buying_date,omitempty"` EndDate *time.Time `json:"end_buying_date,omitempty" bson:"end_buying_date,omitempty"`
ResourceID string `json:"resource_id" bson:"resource_id" validate:"required"` ResourceID string `json:"resource_id" bson:"resource_id" validate:"required"`

View File

@@ -1,15 +1,20 @@
package resources package resources
import ( import (
"encoding/json"
"errors"
"slices" "slices"
"cloud.o-forge.io/core/oc-lib/config" "cloud.o-forge.io/core/oc-lib/config"
"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/common/models" "cloud.o-forge.io/core/oc-lib/models/common/models"
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/peer" "cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/biter777/countries" "github.com/biter777/countries"
"github.com/google/uuid"
) )
// AbstractResource is the struct containing all of the attributes commons to all ressources // AbstractResource is the struct containing all of the attributes commons to all ressources
@@ -21,10 +26,27 @@ type AbstractResource struct {
ShortDescription string `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource ShortDescription string `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource
Owners []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"` // Owners is the list of owners of the resource Owners []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"` // Owners is the list of owners of the resource
UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"` UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"`
SelectedInstanceIndex *int `json:"selected_instance_index,omitempty" bson:"selected_instance_index,omitempty"` // SelectedInstance is the selected instance AllowedBookingModes map[booking.BookingMode]*pricing.PricingVariation `bson:"allowed_booking_modes" json:"allowed_booking_modes"`
} }
func (r *AbstractResource) GetSelectedInstance() ResourceInstanceITF { func (abs *AbstractResource) FilterPeer(peerID string) *dbs.Filters {
return nil
}
func (r *AbstractResource) GetBookingModes() map[booking.BookingMode]*pricing.PricingVariation {
if len(r.AllowedBookingModes) == 0 {
return map[booking.BookingMode]*pricing.PricingVariation{
booking.PLANNED: {
Percentage: 0,
}, booking.WHEN_POSSIBLE: {
Percentage: 0,
},
}
}
return r.AllowedBookingModes
}
func (r *AbstractResource) GetSelectedInstance(selected *int) ResourceInstanceITF {
return nil return nil
} }
@@ -52,29 +74,62 @@ type AbstractInstanciatedResource[T ResourceInstanceITF] struct {
Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource
} }
func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { // PEERID found
func (abs *AbstractInstanciatedResource[T]) RefineResourceByPartnership(peerID string) ResourceInterface {
instances := []T{}
for _, i := range instances {
i, ok := i.RefineResourceByPartnership(peerID)
if ok {
instances = append(instances, i.(T))
}
}
abs.Instances = instances
return abs
}
func (abs *AbstractInstanciatedResource[T]) AddInstances(instance ResourceInstanceITF) {
abs.Instances = append(abs.Instances, instance.(T))
}
func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) {
instances := map[string]string{} instances := map[string]string{}
profiles := []pricing.PricingProfileITF{} profiles := []pricing.PricingProfileITF{}
for _, instance := range abs.Instances { for _, instance := range abs.Instances { // TODO why it crush before ?
instances[instance.GetID()] = instance.GetName() instances[instance.GetID()] = instance.GetName()
profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups) profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups)
} }
var profile pricing.PricingProfileITF var profile pricing.PricingProfileITF
if t := abs.GetSelectedInstance(); t != nil { if t := abs.GetSelectedInstance(selectedInstance); t != nil {
profile = t.GetProfile() profile = t.GetProfile(request.PeerID, selectedPartnership, selectedBuyingStrategy, selectedStrategy)
} }
if profile == nil && len(profiles) > 0 { if profile == nil {
if len(profiles) > 0 {
profile = profiles[0] profile = profiles[0]
} else {
if ok, _ := utils.IsMySelf(request.PeerID, (&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
})); ok {
profile = pricing.GetDefaultPricingProfile()
} else {
return nil, errors.New("no pricing profile found")
}
}
}
variations := []*pricing.PricingVariation{}
if selectedBookingModeIndex != nil && abs.AllowedBookingModes[booking.BookingMode(*selectedBookingModeIndex)] != nil {
variations = append(variations, abs.AllowedBookingModes[booking.BookingMode(*selectedBookingModeIndex)])
} }
return &PricedResource{ return &PricedResource{
Name: abs.Name, Name: abs.Name,
Logo: abs.Logo, Logo: abs.Logo,
ResourceID: abs.UUID, ResourceID: abs.UUID,
ResourceType: t, ResourceType: t,
Quantity: 1,
InstancesRefs: instances, InstancesRefs: instances,
SelectedPricing: profile, SelectedPricing: profile,
Variations: variations,
CreatorID: abs.CreatorID, CreatorID: abs.CreatorID,
} }, nil
} }
func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject { func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject {
@@ -84,9 +139,9 @@ func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject {
return abs return abs
} }
func (r *AbstractInstanciatedResource[T]) GetSelectedInstance() ResourceInstanceITF { func (r *AbstractInstanciatedResource[T]) GetSelectedInstance(selected *int) ResourceInstanceITF {
if r.SelectedInstanceIndex != nil && len(r.Instances) > *r.SelectedInstanceIndex { if selected != nil && len(r.Instances) > *selected {
return r.Instances[*r.SelectedInstanceIndex] return r.Instances[*selected]
} }
if len(r.Instances) > 0 { if len(r.Instances) > 0 {
return r.Instances[0] return r.Instances[0]
@@ -98,20 +153,22 @@ func (abs *AbstractInstanciatedResource[T]) SetAllowedInstances(request *tools.A
if request != nil && request.PeerID == abs.CreatorID && request.PeerID != "" { if request != nil && request.PeerID == abs.CreatorID && request.PeerID != "" {
return return
} }
abs.Instances = VerifyAuthAction[T](abs.Instances, request) abs.Instances = VerifyAuthAction(abs.Instances, request)
} }
func (d *AbstractInstanciatedResource[T]) Trim() { func (d *AbstractInstanciatedResource[T]) Trim() {
d.Type = d.GetType() d.Type = d.GetType()
if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.CreatorID}}).IsMySelf(); !ok { if ok, _ := utils.IsMySelf(d.CreatorID, (&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
})); !ok {
for _, instance := range d.Instances { for _, instance := range d.Instances {
instance.ClearPeerGroups() instance.ClearPeerGroups()
} }
} }
} }
func (abs *AbstractInstanciatedResource[T]) VerifyAuth(request *tools.APIRequest) bool { func (abs *AbstractInstanciatedResource[T]) VerifyAuth(callName string, request *tools.APIRequest) bool {
return len(VerifyAuthAction[T](abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(request) return len(VerifyAuthAction(abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(callName, request)
} }
func VerifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.APIRequest) []T { func VerifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.APIRequest) []T {
@@ -151,52 +208,66 @@ type ResourceInstance[T ResourcePartnerITF] struct {
Env []models.Param `json:"env,omitempty" bson:"env,omitempty"` Env []models.Param `json:"env,omitempty" bson:"env,omitempty"`
Inputs []models.Param `json:"inputs,omitempty" bson:"inputs,omitempty"` Inputs []models.Param `json:"inputs,omitempty" bson:"inputs,omitempty"`
Outputs []models.Param `json:"outputs,omitempty" bson:"outputs,omitempty"` Outputs []models.Param `json:"outputs,omitempty" bson:"outputs,omitempty"`
SelectedPartnershipIndex int `json:"selected_partnership_index,omitempty" bson:"selected_partnership_index,omitempty"`
SelectedBuyingStrategy int `json:"selected_buying_strategy,omitempty" bson:"selected_buying_strategy,omitempty"`
SelectedStrategy int `json:"selected_strategy,omitempty" bson:"selected_strategy,omitempty"`
Partnerships []T `json:"partnerships,omitempty" bson:"partnerships,omitempty"` Partnerships []T `json:"partnerships,omitempty" bson:"partnerships,omitempty"`
} }
// TODO should kicks all selection
func NewInstance[T ResourcePartnerITF](name string) *ResourceInstance[T] {
return &ResourceInstance[T]{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: name,
},
Partnerships: []T{},
}
}
func (abs *ResourceInstance[T]) RefineResourceByPartnership(peerID string) (ResourceInstanceITF, bool) {
okk := false
partners := []T{}
for _, p := range abs.Partnerships {
partner, ok := p.RefineResourceByPartnership(peerID)
if ok {
partners = append(partners, partner.(T))
okk = true
}
}
return abs, okk
}
func (ri *ResourceInstance[T]) ClearEnv() { func (ri *ResourceInstance[T]) ClearEnv() {
ri.Env = []models.Param{} ri.Env = []models.Param{}
ri.Inputs = []models.Param{} ri.Inputs = []models.Param{}
ri.Outputs = []models.Param{} ri.Outputs = []models.Param{}
} }
func (ri *ResourceInstance[T]) GetProfile() pricing.PricingProfileITF { func (ri *ResourceInstance[T]) GetProfile(peerID string, partnershipIndex *int, buyingIndex *int, strategyIndex *int) pricing.PricingProfileITF {
if len(ri.Partnerships) > ri.SelectedPartnershipIndex { if partnershipIndex != nil && len(ri.Partnerships) > *partnershipIndex {
prts := ri.Partnerships[ri.SelectedPartnershipIndex] prts := ri.Partnerships[*partnershipIndex]
return prts.GetProfile(ri.SelectedBuyingStrategy, ri.SelectedBuyingStrategy) return prts.GetProfile(buyingIndex, strategyIndex)
}
if ok, _ := utils.IsMySelf(peerID, (&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
})); ok {
return pricing.GetDefaultPricingProfile()
} }
return nil return nil
} }
func (ri *ResourceInstance[T]) GetSelectedPartnership(peerID string, groups []string) ResourcePartnerITF {
if len(ri.Partnerships) > ri.SelectedPartnershipIndex {
return ri.Partnerships[ri.SelectedPartnershipIndex]
}
return nil
}
func (ri *ResourceInstance[T]) GetPartnerships(peerID string, groups []string) []ResourcePartnerITF {
partners := []ResourcePartnerITF{}
for _, p := range ri.Partnerships {
if p.GetPeerGroups()[peerID] != nil {
for _, g := range p.GetPeerGroups()[peerID] {
if slices.Contains(groups, g) {
partners = append(partners, p)
}
}
}
}
return partners
}
func (ri *ResourceInstance[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF { func (ri *ResourceInstance[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
pricings := []pricing.PricingProfileITF{} pricings := []pricing.PricingProfileITF{}
for _, p := range ri.Partnerships { for _, p := range ri.Partnerships {
pricings = append(pricings, p.GetPricingsProfiles(peerID, groups)...) pricings = append(pricings, p.GetPricingsProfiles(peerID, groups)...)
} }
if len(pricings) == 0 {
if ok, _ := utils.IsMySelf(peerID, (&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
})); ok {
pricings = append(pricings, pricing.GetDefaultPricingProfile())
}
}
return pricings return pricings
} }
@@ -207,6 +278,20 @@ func (ri *ResourceInstance[T]) GetPeerGroups() ([]ResourcePartnerITF, []map[stri
partners = append(partners, p) partners = append(partners, p)
groups = append(groups, p.GetPeerGroups()) groups = append(groups, p.GetPeerGroups())
} }
if len(groups) == 0 {
id, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
}))
if err != nil {
return partners, groups
}
groups = []map[string][]string{
{
id: {"*"},
},
}
// TODO make allow all only for self.
}
return partners, groups return partners, groups
} }
@@ -223,12 +308,28 @@ type ResourcePartnerShip[T pricing.PricingProfileITF] struct {
// to upgrade pricing profiles. to be a map BuyingStrategy, map of Strategy // to upgrade pricing profiles. to be a map BuyingStrategy, map of Strategy
} }
func (ri *ResourcePartnerShip[T]) GetProfile(buying int, strategy int) pricing.PricingProfileITF { func (ri *ResourcePartnerShip[T]) RefineResourceByPartnership(peerID string) (ResourcePartnerITF, bool) {
if strat, ok := ri.PricingProfiles[buying]; ok { ok := false
if profile, ok := strat[strategy]; ok { peerGrp := map[string][]string{}
for k, v := range ri.PeerGroups {
if k == peerID {
peerGrp[k] = v
ok = true
}
}
ri.PeerGroups = peerGrp
return ri, ok
}
func (ri *ResourcePartnerShip[T]) GetProfile(buying *int, strategy *int) pricing.PricingProfileITF {
if buying != nil && strategy != nil {
if strat, ok := ri.PricingProfiles[*buying]; ok {
if profile, ok := strat[*strategy]; ok {
return profile return profile
} }
} }
}
return nil return nil
} }
@@ -254,13 +355,70 @@ func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []st
return profiles return profiles
} }
} }
if len(profiles) == 0 {
if ok, _ := utils.IsMySelf(peerID, (&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
})); ok {
profiles = append(profiles, pricing.GetDefaultPricingProfile())
}
}
return profiles return profiles
} }
func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string { func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string {
if len(rp.PeerGroups) == 0 {
id, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
}))
if err != nil {
return rp.PeerGroups
}
return map[string][]string{
id: {"*"},
}
}
return rp.PeerGroups return rp.PeerGroups
} }
func (rp *ResourcePartnerShip[T]) ClearPeerGroups() { func (rp *ResourcePartnerShip[T]) ClearPeerGroups() {
rp.PeerGroups = map[string][]string{} rp.PeerGroups = map[string][]string{}
} }
func ToResource(
dt int,
payload []byte,
) (ResourceInterface, error) {
switch dt {
case tools.PROCESSING_RESOURCE.EnumIndex():
var data ProcessingResource
if err := json.Unmarshal(payload, &data); err != nil {
return nil, err
}
return &data, nil
case tools.WORKFLOW_RESOURCE.EnumIndex():
var data WorkflowResource
if err := json.Unmarshal(payload, &data); err != nil {
return nil, err
}
return &data, nil
case tools.DATA_RESOURCE.EnumIndex():
var data DataResource
if err := json.Unmarshal(payload, &data); err != nil {
return nil, err
}
return &data, nil
case tools.STORAGE_RESOURCE.EnumIndex():
var data StorageResource
if err := json.Unmarshal(payload, &data); err != nil {
return nil, err
}
return &data, nil
case tools.COMPUTE_RESOURCE.EnumIndex():
var data ComputeResource
if err := json.Unmarshal(payload, &data); err != nil {
return nil, err
}
return &data, nil
}
return nil, errors.New("can't found any data resources matching")
}

View File

@@ -20,7 +20,7 @@ func NewAccessor[T ResourceInterface](t tools.DataType, request *tools.APIReques
if !slices.Contains([]tools.DataType{ if !slices.Contains([]tools.DataType{
tools.COMPUTE_RESOURCE, tools.STORAGE_RESOURCE, tools.COMPUTE_RESOURCE, tools.STORAGE_RESOURCE,
tools.PROCESSING_RESOURCE, tools.WORKFLOW_RESOURCE, tools.PROCESSING_RESOURCE, tools.WORKFLOW_RESOURCE,
tools.DATA_RESOURCE, tools.DATA_RESOURCE, tools.NATIVE_TOOL,
}, t) { }, t) {
return nil return nil
} }

View File

@@ -10,6 +10,7 @@ import (
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
) )
/* /*
@@ -30,15 +31,18 @@ func (r *StorageResource) GetType() string {
return tools.STORAGE_RESOURCE.String() return tools.STORAGE_RESOURCE.String()
} }
func (abs *StorageResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { func (abs *StorageResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) {
if t != tools.STORAGE_RESOURCE { if t != tools.STORAGE_RESOURCE {
return nil return nil, errors.New("not the proper type expected : cannot convert to priced resource : have " + t.String() + " wait Storage")
}
p, err := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, selectedInstance, selectedPartnership, selectedBuyingStrategy, selectedStrategy, selectedBookingModeIndex, request)
if err != nil {
return nil, err
} }
p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
priced := p.(*PricedResource) priced := p.(*PricedResource)
return &PricedStorageResource{ return &PricedStorageResource{
PricedResource: *priced, PricedResource: *priced,
} }, nil
} }
type StorageResourceInstance struct { type StorageResourceInstance struct {
@@ -54,6 +58,17 @@ type StorageResourceInstance struct {
Throughput string `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage Throughput string `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage
} }
func NewStorageResourceInstance(name string, peerID string) ResourceInstanceITF {
return &StorageResourceInstance{
ResourceInstance: ResourceInstance[*StorageResourcePartnership]{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: name,
},
},
}
}
func (ri *StorageResourceInstance) ClearEnv() { func (ri *StorageResourceInstance) ClearEnv() {
ri.Env = []models.Param{} ri.Env = []models.Param{}
ri.Inputs = []models.Param{} ri.Inputs = []models.Param{}
@@ -115,11 +130,15 @@ func StorageResourcePricingStrategyList() []StorageResourcePricingStrategy {
} }
func (t StorageResourcePricingStrategy) String() string { func (t StorageResourcePricingStrategy) String() string {
return [...]string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}[t] l := pricing.TimePricingStrategyListStr()
l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
return l[t]
} }
func (t StorageResourcePricingStrategy) GetStrategy() string { func (t StorageResourcePricingStrategy) GetStrategy() string {
return [...]string{"PER_DATA_STORED", "PER_GB_STORED", "PER_MB_STORED", "PER_KB_STORED"}[t] l := pricing.TimePricingStrategyListStr()
l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
return l[t]
} }
func (t StorageResourcePricingStrategy) GetStrategyValue() int { func (t StorageResourcePricingStrategy) GetStrategyValue() int {
@@ -150,10 +169,6 @@ type StorageResourcePricingProfile struct {
pricing.ExploitPricingProfile[StorageResourcePricingStrategy] // ExploitPricingProfile is the pricing profile of a storage it means that we exploit the resource for an amount of continuous time pricing.ExploitPricingProfile[StorageResourcePricingStrategy] // ExploitPricingProfile is the pricing profile of a storage it means that we exploit the resource for an amount of continuous time
} }
func (p *StorageResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
return p.Pricing.BuyingStrategy
}
func (p *StorageResourcePricingProfile) IsPurchasable() bool { func (p *StorageResourcePricingProfile) IsPurchasable() bool {
return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
} }
@@ -165,10 +180,6 @@ func (p *StorageResourcePricingProfile) IsBooked() bool {
return true return true
} }
func (p *StorageResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) {
return p.Pricing.GetPrice(amountOfData, val, start, &end)
}
type PricedStorageResource struct { type PricedStorageResource struct {
PricedResource PricedResource
UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"` UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
@@ -178,15 +189,18 @@ func (r *PricedStorageResource) GetType() tools.DataType {
return tools.STORAGE_RESOURCE return tools.STORAGE_RESOURCE
} }
func (r *PricedStorageResource) GetPrice() (float64, error) { func (r *PricedStorageResource) GetPriceHT() (float64, error) {
fmt.Println("GetPrice", r.UsageStart, r.UsageEnd) if r.BookingConfiguration == nil {
now := time.Now() r.BookingConfiguration = &BookingConfiguration{}
if r.UsageStart == nil {
r.UsageStart = &now
} }
if r.UsageEnd == nil { fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd)
add := r.UsageStart.Add(time.Duration(1 * time.Hour)) now := time.Now()
r.UsageEnd = &add if r.BookingConfiguration.UsageStart == nil {
r.BookingConfiguration.UsageStart = &now
}
if r.BookingConfiguration.UsageEnd == nil {
add := r.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour))
r.BookingConfiguration.UsageEnd = &add
} }
if r.SelectedPricing == nil { if r.SelectedPricing == nil {
return 0, errors.New("pricing profile must be set on Priced Storage" + r.ResourceID) return 0, errors.New("pricing profile must be set on Priced Storage" + r.ResourceID)
@@ -200,5 +214,6 @@ func (r *PricedStorageResource) GetPrice() (float64, error) {
return 0, err return 0, err
} }
} }
return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd) return pricing.GetPriceHT(amountOfData, r.BookingConfiguration.ExplicitBookingDurationS,
*r.BookingConfiguration.UsageStart, *r.BookingConfiguration.UsageEnd, r.Variations)
} }

View File

@@ -30,12 +30,12 @@ func TestComputeResource_ConvertToPricedResource(t *testing.T) {
cr := &resources.ComputeResource{} cr := &resources.ComputeResource{}
cr.UUID = "comp123" cr.UUID = "comp123"
cr.AbstractInstanciatedResource.UUID = cr.UUID cr.AbstractInstanciatedResource.UUID = cr.UUID
result := cr.ConvertToPricedResource(tools.COMPUTE_RESOURCE, req) result, _ := cr.ConvertToPricedResource(tools.COMPUTE_RESOURCE, nil, nil, nil, nil, nil, req)
assert.NotNil(t, result) assert.NotNil(t, result)
assert.IsType(t, &resources.PricedComputeResource{}, result) assert.IsType(t, &resources.PricedComputeResource{}, result)
} }
func TestComputeResourcePricingProfile_GetPrice_CPUs(t *testing.T) { func TestComputeResourcePricingProfile_GetPriceHT_CPUs(t *testing.T) {
start := time.Now() start := time.Now()
end := start.Add(1 * time.Hour) end := start.Add(1 * time.Hour)
profile := resources.ComputeResourcePricingProfile{ profile := resources.ComputeResourcePricingProfile{
@@ -47,45 +47,47 @@ func TestComputeResourcePricingProfile_GetPrice_CPUs(t *testing.T) {
}, },
} }
price, err := profile.GetPrice(2, 3600, start, end, "cpus", "Xeon") price, err := profile.GetPriceHT(2, 3600, start, end, []*pricing.PricingVariation{}, "cpus", "Xeon")
require.NoError(t, err) require.NoError(t, err)
assert.Greater(t, price, float64(0)) assert.Greater(t, price, float64(0))
} }
func TestComputeResourcePricingProfile_GetPrice_InvalidParams(t *testing.T) { func TestComputeResourcePricingProfile_GetPriceHT_InvalidParams(t *testing.T) {
profile := resources.ComputeResourcePricingProfile{} profile := resources.ComputeResourcePricingProfile{}
_, err := profile.GetPrice(1, 3600, time.Now(), time.Now()) _, err := profile.GetPriceHT(1, 3600, time.Now(), time.Now(), []*pricing.PricingVariation{})
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, "params must be set", err.Error()) assert.Equal(t, "params must be set", err.Error())
} }
func TestPricedComputeResource_GetPrice(t *testing.T) { func TestPricedComputeResource_GetPriceHT(t *testing.T) {
start := time.Now() start := time.Now()
end := start.Add(1 * time.Hour) end := start.Add(1 * time.Hour)
r := resources.PricedComputeResource{ r := resources.PricedComputeResource{
PricedResource: resources.PricedResource{ PricedResource: resources.PricedResource{
ResourceID: "comp456", ResourceID: "comp456",
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &start, UsageStart: &start,
UsageEnd: &end, UsageEnd: &end,
ExplicitBookingDurationS: 3600, ExplicitBookingDurationS: 3600,
}, },
},
CPUsLocated: map[string]float64{"Xeon": 2}, CPUsLocated: map[string]float64{"Xeon": 2},
GPUsLocated: map[string]float64{"Tesla": 1}, GPUsLocated: map[string]float64{"Tesla": 1},
RAMLocated: 4, RAMLocated: 4,
} }
price, err := r.GetPrice() price, err := r.GetPriceHT()
require.NoError(t, err) require.NoError(t, err)
assert.Greater(t, price, float64(0)) assert.Greater(t, price, float64(0))
} }
func TestPricedComputeResource_GetPrice_MissingProfile(t *testing.T) { func TestPricedComputeResource_GetPriceHT_MissingProfile(t *testing.T) {
r := resources.PricedComputeResource{ r := resources.PricedComputeResource{
PricedResource: resources.PricedResource{ PricedResource: resources.PricedResource{
ResourceID: "comp789", ResourceID: "comp789",
}, },
} }
_, err := r.GetPrice() _, err := r.GetPriceHT()
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.Error(), "pricing profile must be set") assert.Contains(t, err.Error(), "pricing profile must be set")
} }

View File

@@ -27,10 +27,10 @@ func TestDataResource_GetAccessor(t *testing.T) {
func TestDataResource_ConvertToPricedResource(t *testing.T) { func TestDataResource_ConvertToPricedResource(t *testing.T) {
d := &resources.DataResource{} d := &resources.DataResource{}
d.UUID = "123" d.UUID = "123"
res := d.ConvertToPricedResource(tools.DATA_RESOURCE, &tools.APIRequest{}) res, _ := d.ConvertToPricedResource(tools.DATA_RESOURCE, nil, nil, nil, nil, nil, &tools.APIRequest{})
assert.IsType(t, &resources.PricedDataResource{}, res) assert.IsType(t, &resources.PricedDataResource{}, res)
nilRes := d.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &tools.APIRequest{}) nilRes, _ := d.ConvertToPricedResource(tools.PROCESSING_RESOURCE, nil, nil, nil, nil, nil, &tools.APIRequest{})
assert.Nil(t, nilRes) assert.Nil(t, nilRes)
} }
@@ -80,7 +80,7 @@ func TestDataResourcePricingProfile_IsPurchased(t *testing.T) {
assert.True(t, profile.IsPurchasable()) assert.True(t, profile.IsPurchasable())
} }
func TestPricedDataResource_GetPrice(t *testing.T) { func TestPricedDataResource_GetPriceHT(t *testing.T) {
now := time.Now() now := time.Now()
later := now.Add(1 * time.Hour) later := now.Add(1 * time.Hour)
mockPrice := 42.0 mockPrice := 42.0
@@ -92,23 +92,25 @@ func TestPricedDataResource_GetPrice(t *testing.T) {
r := &resources.PricedDataResource{ r := &resources.PricedDataResource{
PricedResource: resources.PricedResource{ PricedResource: resources.PricedResource{
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &now, UsageStart: &now,
UsageEnd: &later, UsageEnd: &later,
}, },
},
} }
price, err := r.GetPrice() price, err := r.GetPriceHT()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, mockPrice, price) assert.Equal(t, mockPrice, price)
} }
func TestPricedDataResource_GetPrice_NoProfiles(t *testing.T) { func TestPricedDataResource_GetPriceHT_NoProfiles(t *testing.T) {
r := &resources.PricedDataResource{ r := &resources.PricedDataResource{
PricedResource: resources.PricedResource{ PricedResource: resources.PricedResource{
ResourceID: "test-resource", ResourceID: "test-resource",
}, },
} }
_, err := r.GetPrice() _, err := r.GetPriceHT()
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), "pricing profile must be set") assert.Contains(t, err.Error(), "pricing profile must be set")
} }

View File

@@ -26,7 +26,7 @@ func (m *MockPricingProfile) IsPurchasable() bool {
return m.Purchased return m.Purchased
} }
func (m *MockPricingProfile) GetPrice(amount float64, explicitDuration float64, start time.Time, end time.Time, _ ...string) (float64, error) { func (m *MockPricingProfile) GetPriceHT(amount float64, explicitDuration float64, start time.Time, end time.Time, variations []*pricing.PricingVariation, _ ...string) (float64, error) {
if m.ReturnErr { if m.ReturnErr {
return 0, errors.New("mock error") return 0, errors.New("mock error")
} }
@@ -72,14 +72,21 @@ func TestGetAndSetLocationStartEnd(t *testing.T) {
func TestGetExplicitDurationInS(t *testing.T) { func TestGetExplicitDurationInS(t *testing.T) {
t.Run("uses explicit duration if set", func(t *testing.T) { t.Run("uses explicit duration if set", func(t *testing.T) {
r := &resources.PricedResource{ExplicitBookingDurationS: 3600} r := &resources.PricedResource{BookingConfiguration: &resources.BookingConfiguration{
ExplicitBookingDurationS: 3600,
},
}
assert.Equal(t, 3600.0, r.GetExplicitDurationInS()) assert.Equal(t, 3600.0, r.GetExplicitDurationInS())
}) })
t.Run("computes duration from start and end", func(t *testing.T) { t.Run("computes duration from start and end", func(t *testing.T) {
start := time.Now() start := time.Now()
end := start.Add(2 * time.Hour) end := start.Add(2 * time.Hour)
r := &resources.PricedResource{UsageStart: &start, UsageEnd: &end} r := &resources.PricedResource{
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &start, UsageEnd: &end,
},
}
assert.InDelta(t, 7200.0, r.GetExplicitDurationInS(), 0.1) assert.InDelta(t, 7200.0, r.GetExplicitDurationInS(), 0.1)
}) })
@@ -89,10 +96,10 @@ func TestGetExplicitDurationInS(t *testing.T) {
}) })
} }
func TestGetPrice(t *testing.T) { func TestGetPriceHT(t *testing.T) {
t.Run("returns error if no pricing profile", func(t *testing.T) { t.Run("returns error if no pricing profile", func(t *testing.T) {
r := &resources.PricedResource{ResourceID: "no-profile"} r := &resources.PricedResource{ResourceID: "no-profile"}
price, err := r.GetPrice() price, err := r.GetPriceHT()
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.Error(), "pricing profile must be set") assert.Contains(t, err.Error(), "pricing profile must be set")
assert.Equal(t, 0.0, price) assert.Equal(t, 0.0, price)
@@ -102,24 +109,28 @@ func TestGetPrice(t *testing.T) {
start := time.Now() start := time.Now()
end := start.Add(30 * time.Minute) end := start.Add(30 * time.Minute)
r := &resources.PricedResource{ r := &resources.PricedResource{
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &start, UsageStart: &start,
UsageEnd: &end, UsageEnd: &end,
},
} }
price, err := r.GetPrice() price, err := r.GetPriceHT()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 42.0, price) assert.Equal(t, 42.0, price)
}) })
t.Run("returns error if profile GetPrice fails", func(t *testing.T) { t.Run("returns error if profile GetPriceHT fails", func(t *testing.T) {
start := time.Now() start := time.Now()
end := start.Add(1 * time.Hour) end := start.Add(1 * time.Hour)
mock := &MockPricingProfile{ReturnErr: true} mock := &MockPricingProfile{ReturnErr: true}
r := &resources.PricedResource{ r := &resources.PricedResource{
SelectedPricing: mock, SelectedPricing: mock,
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &start, UsageStart: &start,
UsageEnd: &end, UsageEnd: &end,
},
} }
price, err := r.GetPrice() price, err := r.GetPriceHT()
require.Error(t, err) require.Error(t, err)
assert.Equal(t, 0.0, price) assert.Equal(t, 0.0, price)
}) })
@@ -130,10 +141,12 @@ func TestGetPrice(t *testing.T) {
mock := &MockPricingProfile{ReturnCost: 10.0} mock := &MockPricingProfile{ReturnCost: 10.0}
r := &resources.PricedResource{ r := &resources.PricedResource{
SelectedPricing: mock, SelectedPricing: mock,
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &start, UsageStart: &start,
UsageEnd: &end, UsageEnd: &end,
},
} }
price, err := r.GetPrice() price, err := r.GetPriceHT()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 10.0, price) assert.Equal(t, 10.0, price)
}) })

View File

@@ -5,6 +5,7 @@ import (
"time" "time"
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/resources"
. "cloud.o-forge.io/core/oc-lib/models/resources" . "cloud.o-forge.io/core/oc-lib/models/resources"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -40,28 +41,34 @@ func TestPricedProcessingResource_GetExplicitDurationInS(t *testing.T) {
name: "Nil start time, non-service", name: "Nil start time, non-service",
input: PricedProcessingResource{ input: PricedProcessingResource{
PricedResource: PricedResource{ PricedResource: PricedResource{
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: nil, UsageStart: nil,
}, },
}, },
},
expected: float64((1 * time.Hour).Seconds()), expected: float64((1 * time.Hour).Seconds()),
}, },
{ {
name: "Duration computed from start and end", name: "Duration computed from start and end",
input: PricedProcessingResource{ input: PricedProcessingResource{
PricedResource: PricedResource{ PricedResource: PricedResource{
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &now, UsageStart: &now,
UsageEnd: &after, UsageEnd: &after,
}, },
}, },
},
expected: float64((2 * time.Hour).Seconds()), expected: float64((2 * time.Hour).Seconds()),
}, },
{ {
name: "Explicit duration takes precedence", name: "Explicit duration takes precedence",
input: PricedProcessingResource{ input: PricedProcessingResource{
PricedResource: PricedResource{ PricedResource: PricedResource{
BookingConfiguration: &resources.BookingConfiguration{
ExplicitBookingDurationS: 1337, ExplicitBookingDurationS: 1337,
}, },
}, },
},
expected: 1337, expected: 1337,
}, },
} }
@@ -80,7 +87,7 @@ func TestProcessingResource_GetAccessor(t *testing.T) {
assert.NotNil(t, acc) assert.NotNil(t, acc)
} }
func TestProcessingResourcePricingProfile_GetPrice(t *testing.T) { func TestProcessingResourcePricingProfile_GetPriceHT(t *testing.T) {
start := time.Now() start := time.Now()
end := start.Add(2 * time.Hour) end := start.Add(2 * time.Hour)
mockPricing := pricing.AccessPricingProfile[pricing.TimePricingStrategy]{ mockPricing := pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
@@ -88,8 +95,8 @@ func TestProcessingResourcePricingProfile_GetPrice(t *testing.T) {
Price: 100.0, Price: 100.0,
}, },
} }
profile := &ProcessingResourcePricingProfile{mockPricing} profile := &ProcessingResourcePricingProfile{AccessPricingProfile: mockPricing}
price, err := profile.GetPrice(0, 0, start, end) price, err := profile.GetPriceHT(0, 0, start, end, []*pricing.PricingVariation{})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 100.0, price) assert.Equal(t, 100.0, price)
} }

View File

@@ -20,7 +20,7 @@ func (m *MockInstance) GetID() string { return m.ID }
func (m *MockInstance) GetName() string { return m.Name } func (m *MockInstance) GetName() string { return m.Name }
func (m *MockInstance) ClearEnv() {} func (m *MockInstance) ClearEnv() {}
func (m *MockInstance) ClearPeerGroups() {} func (m *MockInstance) ClearPeerGroups() {}
func (m *MockInstance) GetProfile() pricing.PricingProfileITF { func (m *MockInstance) GetProfile(peerID string, a *int, b *int, c *int) pricing.PricingProfileITF {
return nil return nil
} }
func (m *MockInstance) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF { func (m *MockInstance) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
@@ -36,7 +36,11 @@ type MockPartner struct {
groups map[string][]string groups map[string][]string
} }
func (m *MockPartner) GetProfile(buying int, strategy int) pricing.PricingProfileITF { func (abs *MockPartner) RefineResourceByPartnership(peerID string) (resources.ResourcePartnerITF, bool) {
return nil, false
}
func (m *MockPartner) GetProfile(buying *int, strategy *int) pricing.PricingProfileITF {
return nil return nil
} }
@@ -62,10 +66,10 @@ func TestGetSelectedInstance_WithValidIndex(t *testing.T) {
inst1 := &MockInstance{ID: "1"} inst1 := &MockInstance{ID: "1"}
inst2 := &MockInstance{ID: "2"} inst2 := &MockInstance{ID: "2"}
resource := &resources.AbstractInstanciatedResource[*MockInstance]{ resource := &resources.AbstractInstanciatedResource[*MockInstance]{
AbstractResource: resources.AbstractResource{SelectedInstanceIndex: &index}, AbstractResource: resources.AbstractResource{},
Instances: []*MockInstance{inst1, inst2}, Instances: []*MockInstance{inst1, inst2},
} }
result := resource.GetSelectedInstance() result := resource.GetSelectedInstance(&index)
assert.Equal(t, inst2, result) assert.Equal(t, inst2, result)
} }
@@ -74,7 +78,7 @@ func TestGetSelectedInstance_NoIndex(t *testing.T) {
resource := &resources.AbstractInstanciatedResource[*MockInstance]{ resource := &resources.AbstractInstanciatedResource[*MockInstance]{
Instances: []*MockInstance{inst}, Instances: []*MockInstance{inst},
} }
result := resource.GetSelectedInstance() result := resource.GetSelectedInstance(nil)
assert.Equal(t, inst, result) assert.Equal(t, inst, result)
} }
@@ -105,7 +109,7 @@ type FakeResource struct {
func (f *FakeResource) Trim() {} func (f *FakeResource) Trim() {}
func (f *FakeResource) SetAllowedInstances(*tools.APIRequest) {} func (f *FakeResource) SetAllowedInstances(*tools.APIRequest) {}
func (f *FakeResource) VerifyAuth(*tools.APIRequest) bool { return true } func (f *FakeResource) VerifyAuth(string, *tools.APIRequest) bool { return true }
func TestNewAccessor_ReturnsValid(t *testing.T) { func TestNewAccessor_ReturnsValid(t *testing.T) {
acc := resources.NewAccessor[*FakeResource](tools.COMPUTE_RESOURCE, &tools.APIRequest{}, func() utils.DBObject { acc := resources.NewAccessor[*FakeResource](tools.COMPUTE_RESOURCE, &tools.APIRequest{}, func() utils.DBObject {

View File

@@ -26,14 +26,14 @@ func TestStorageResource_ConvertToPricedResource_ValidType(t *testing.T) {
res := &resources.StorageResource{} res := &resources.StorageResource{}
res.AbstractInstanciatedResource.CreatorID = "creator" res.AbstractInstanciatedResource.CreatorID = "creator"
res.AbstractInstanciatedResource.UUID = "res-id" res.AbstractInstanciatedResource.UUID = "res-id"
priced := res.ConvertToPricedResource(tools.STORAGE_RESOURCE, &tools.APIRequest{}) priced, _ := res.ConvertToPricedResource(tools.STORAGE_RESOURCE, nil, nil, nil, nil, nil, &tools.APIRequest{})
assert.NotNil(t, priced) assert.NotNil(t, priced)
assert.IsType(t, &resources.PricedStorageResource{}, priced) assert.IsType(t, &resources.PricedStorageResource{}, priced)
} }
func TestStorageResource_ConvertToPricedResource_InvalidType(t *testing.T) { func TestStorageResource_ConvertToPricedResource_InvalidType(t *testing.T) {
res := &resources.StorageResource{} res := &resources.StorageResource{}
priced := res.ConvertToPricedResource(tools.COMPUTE_RESOURCE, &tools.APIRequest{}) priced, _ := res.ConvertToPricedResource(tools.COMPUTE_RESOURCE, nil, nil, nil, nil, nil, &tools.APIRequest{})
assert.Nil(t, priced) assert.Nil(t, priced)
} }
@@ -94,12 +94,12 @@ func TestStorageResourcePricingStrategy_GetQuantity_Invalid(t *testing.T) {
assert.Equal(t, 0.0, q) assert.Equal(t, 0.0, q)
} }
func TestPricedStorageResource_GetPrice_NoProfiles(t *testing.T) { func TestPricedStorageResource_GetPriceHT_NoProfiles(t *testing.T) {
res := &resources.PricedStorageResource{ res := &resources.PricedStorageResource{
PricedResource: resources.PricedResource{ PricedResource: resources.PricedResource{
ResourceID: "res-id", ResourceID: "res-id",
}, },
} }
_, err := res.GetPrice() _, err := res.GetPriceHT()
assert.Error(t, err) assert.Error(t, err)
} }

View File

@@ -32,7 +32,7 @@ func TestWorkflowResource_ConvertToPricedResource(t *testing.T) {
Groups: []string{"group1"}, Groups: []string{"group1"},
} }
pr := w.ConvertToPricedResource(tools.WORKFLOW_RESOURCE, req) pr, _ := w.ConvertToPricedResource(tools.WORKFLOW_RESOURCE, nil, nil, nil, nil, nil, req)
assert.Equal(t, "creator-id", pr.GetCreatorID()) assert.Equal(t, "creator-id", pr.GetCreatorID())
assert.Equal(t, tools.WORKFLOW_RESOURCE, pr.GetType()) assert.Equal(t, tools.WORKFLOW_RESOURCE, pr.GetType())
} }

View File

@@ -19,6 +19,13 @@ func (d *WorkflowResource) GetAccessor(request *tools.APIRequest) utils.Accessor
return NewAccessor[*WorkflowResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} }) return NewAccessor[*WorkflowResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} })
} }
func (abs *WorkflowResource) RefineResourceByPartnership(peerID string) ResourceInterface {
return abs
}
func (r *WorkflowResource) AddInstances(instance ResourceInstanceITF) {
}
func (r *WorkflowResource) GetType() string { func (r *WorkflowResource) GetType() string {
return tools.WORKFLOW_RESOURCE.String() return tools.WORKFLOW_RESOURCE.String()
} }
@@ -34,12 +41,15 @@ func (w *WorkflowResource) SetAllowedInstances(request *tools.APIRequest) {
/* EMPTY */ /* EMPTY */
} }
func (w *WorkflowResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF { func (w *WorkflowResource) ConvertToPricedResource(t tools.DataType, selectedInstance *int, selectedPartnership *int, selectedBuyingStrategy *int, selectedStrategy *int, selectedBookingModeIndex *int, request *tools.APIRequest) (pricing.PricedItemITF, error) {
return &PricedResource{ return &PricedResource{
Name: w.Name, Name: w.Name,
Logo: w.Logo, Logo: w.Logo,
ResourceID: w.UUID, ResourceID: w.UUID,
ResourceType: t, ResourceType: t,
Quantity: 1,
CreatorID: w.CreatorID, CreatorID: w.CreatorID,
} }, nil // TODO ???
} }
// TODO : as instanciated resource !

View File

@@ -47,6 +47,10 @@ func (r *AbstractObject) SetID(id string) {
r.UUID = id r.UUID = id
} }
func (r *AbstractObject) SetName(name string) {
r.Name = name
}
func (r *AbstractObject) GenerateID() { func (r *AbstractObject) GenerateID() {
if r.UUID == "" { if r.UUID == "" {
r.UUID = uuid.New().String() r.UUID = uuid.New().String()
@@ -94,10 +98,12 @@ func (ao *AbstractObject) UpToDate(user string, peer string, create bool) {
} }
} }
func (ao *AbstractObject) VerifyAuth(request *tools.APIRequest) bool { func (ao *AbstractObject) VerifyAuth(callName string, request *tools.APIRequest) bool {
return ao.AccessMode == Public || (request != nil && ao.CreatorID == request.PeerID && request.PeerID != "") return (ao.AccessMode == Public && callName == "get") || request.Admin || (request != nil && ao.CreatorID == request.PeerID && request.PeerID != "")
} }
// TODO : check write per auth
func (ao *AbstractObject) GetObjectFilters(search string) *dbs.Filters { func (ao *AbstractObject) GetObjectFilters(search string) *dbs.Filters {
if search == "*" { if search == "*" {
search = "" search = ""

View File

@@ -2,9 +2,11 @@ package utils
import ( import (
"errors" "errors"
"os"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/dbs/mongo" "cloud.o-forge.io/core/oc-lib/dbs/mongo"
"github.com/google/uuid"
mgb "go.mongodb.org/mongo-driver/mongo" mgb "go.mongodb.org/mongo-driver/mongo"
) )
@@ -18,7 +20,7 @@ func VerifyAccess(a Accessor, id string) error {
if err != nil { if err != nil {
return err return err
} }
if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) { if a.ShouldVerifyAuth() && !data.VerifyAuth("get", a.GetRequest()) {
return errors.New("you are not allowed to access :" + a.GetType().String()) return errors.New("you are not allowed to access :" + a.GetType().String())
} }
return nil return nil
@@ -41,7 +43,7 @@ func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) {
}}, }},
}, },
} }
if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) { if a.ShouldVerifyAuth() && !data.VerifyAuth("store", a.GetRequest()) {
return nil, 403, errors.New("you are not allowed to access : " + a.GetType().String()) return nil, 403, errors.New("you are not allowed to access : " + a.GetType().String())
} }
if cursor, _, _ := a.Search(&f, "", data.IsDrafted()); len(cursor) > 0 { if cursor, _, _ := a.Search(&f, "", data.IsDrafted()); len(cursor) > 0 {
@@ -49,7 +51,7 @@ func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) {
} }
err := validate.Struct(data) err := validate.Struct(data)
if err != nil { if err != nil {
return nil, 422, err return nil, 422, errors.New("error when validating the received struct: " + err.Error())
} }
id, code, err := mongo.MONGOService.StoreOne(data, data.GetID(), a.GetType().String()) id, code, err := mongo.MONGOService.StoreOne(data, data.GetID(), a.GetType().String())
if err != nil { if err != nil {
@@ -68,7 +70,7 @@ func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) {
if err != nil { if err != nil {
return nil, code, err return nil, code, err
} }
if a.ShouldVerifyAuth() && !res.VerifyAuth(a.GetRequest()) { if a.ShouldVerifyAuth() && !res.VerifyAuth("delete", a.GetRequest()) {
return nil, 403, errors.New("you are not allowed to access " + a.GetType().String()) return nil, 403, errors.New("you are not allowed to access " + a.GetType().String())
} }
_, code, err = mongo.MONGOService.DeleteOne(id, a.GetType().String()) _, code, err = mongo.MONGOService.DeleteOne(id, a.GetType().String())
@@ -92,7 +94,7 @@ func GenericUpdateOne(set DBObject, id string, a Accessor, new DBObject) (DBObje
} }
set = newSet set = newSet
r.UpToDate(a.GetUser(), a.GetPeerID(), false) r.UpToDate(a.GetUser(), a.GetPeerID(), false)
if a.ShouldVerifyAuth() && !r.VerifyAuth(a.GetRequest()) { if a.ShouldVerifyAuth() && !r.VerifyAuth("update", a.GetRequest()) {
return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String()) return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String())
} }
change := set.Serialize(set) // get the changes change := set.Serialize(set) // get the changes
@@ -116,7 +118,7 @@ func GenericLoadOne[T DBObject](id string, f func(DBObject) (DBObject, int, erro
return nil, code, err return nil, code, err
} }
res_mongo.Decode(&data) res_mongo.Decode(&data)
if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) { if a.ShouldVerifyAuth() && !data.VerifyAuth("get", a.GetRequest()) {
return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String()) return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String())
} }
return f(data) return f(data)
@@ -132,7 +134,7 @@ func genericLoadAll[T DBObject](res *mgb.Cursor, code int, err error, onlyDraft
return nil, 404, err return nil, 404, err
} }
for _, r := range results { for _, r := range results {
if (a.ShouldVerifyAuth() && !r.VerifyAuth(a.GetRequest())) || f(r) == nil || (onlyDraft && !r.IsDrafted()) || (!onlyDraft && r.IsDrafted()) { if (a.ShouldVerifyAuth() && !r.VerifyAuth("get", a.GetRequest())) || f(r) == nil || (onlyDraft && !r.IsDrafted()) || (!onlyDraft && r.IsDrafted()) {
continue continue
} }
objs = append(objs, f(r)) objs = append(objs, f(r))
@@ -164,3 +166,42 @@ func GenericRawUpdateOne(set DBObject, id string, a Accessor) (DBObject, int, er
} }
return a.LoadOne(id) return a.LoadOne(id)
} }
func GetMySelf(wfa Accessor) (string, error) {
id, err := GenerateNodeID()
if err != nil {
return "", err
}
datas, _, _ := wfa.Search(nil, id, false)
if len(datas) > 0 {
return datas[0].GetID(), nil
}
return "", errors.New("peer not found")
}
func IsMySelf(peerID string, wfa Accessor) (bool, string) {
id, err := GetMySelf(wfa)
if err != nil {
return false, ""
}
return peerID == id, id
}
func GenerateNodeID() (string, error) {
folderStatic := "/var/lib/opencloud-node"
if _, err := os.Stat(folderStatic); err == nil {
os.MkdirAll(folderStatic, 0644)
}
folderStatic += "/node_id"
if _, err := os.Stat(folderStatic); os.IsNotExist(err) {
hostname, err := os.Hostname()
if err != nil {
return "", err
}
id := uuid.NewSHA1(uuid.NameSpaceOID, []byte("oc-"+hostname))
err = os.WriteFile(folderStatic, []byte(id.String()), 0644)
return id.String(), err
}
data, err := os.ReadFile(folderStatic)
return string(data), err
}

View File

@@ -21,13 +21,14 @@ type DBObject interface {
SetID(id string) SetID(id string)
GetID() string GetID() string
GetName() string GetName() string
SetName(name string)
IsDrafted() bool IsDrafted() bool
CanDelete() bool CanDelete() bool
StoreDraftDefault() StoreDraftDefault()
GetCreatorID() string GetCreatorID() string
UpToDate(user string, peer string, create bool) UpToDate(user string, peer string, create bool)
CanUpdate(set DBObject) (bool, DBObject) CanUpdate(set DBObject) (bool, DBObject)
VerifyAuth(request *tools.APIRequest) bool VerifyAuth(callName string, request *tools.APIRequest) bool
Serialize(obj DBObject) map[string]interface{} Serialize(obj DBObject) map[string]interface{}
GetAccessor(request *tools.APIRequest) Accessor GetAccessor(request *tools.APIRequest) Accessor
Deserialize(j map[string]interface{}, obj DBObject) DBObject Deserialize(j map[string]interface{}, obj DBObject) DBObject

View File

@@ -87,14 +87,14 @@ func TestUpToDate_CreateTrue(t *testing.T) {
func TestVerifyAuth(t *testing.T) { func TestVerifyAuth(t *testing.T) {
request := &tools.APIRequest{PeerID: "peer123"} request := &tools.APIRequest{PeerID: "peer123"}
ao := &utils.AbstractObject{CreatorID: "peer123"} ao := &utils.AbstractObject{CreatorID: "peer123"}
assert.True(t, ao.VerifyAuth(request)) assert.True(t, ao.VerifyAuth("get", request))
ao = &utils.AbstractObject{AccessMode: utils.Public} ao = &utils.AbstractObject{AccessMode: utils.Public}
assert.True(t, ao.VerifyAuth(nil)) assert.True(t, ao.VerifyAuth("get", nil))
ao = &utils.AbstractObject{AccessMode: utils.Private, CreatorID: "peer123"} ao = &utils.AbstractObject{AccessMode: utils.Private, CreatorID: "peer123"}
request = &tools.APIRequest{PeerID: "wrong"} request = &tools.APIRequest{PeerID: "wrong"}
assert.False(t, ao.VerifyAuth(request)) assert.False(t, ao.VerifyAuth("get", request))
} }
func TestGetObjectFilters(t *testing.T) { func TestGetObjectFilters(t *testing.T) {

View File

@@ -15,6 +15,15 @@ type Graph struct {
Links []GraphLink `bson:"links" json:"links" default:"{}" validate:"required"` // Links is the list of links between elements in the graph Links []GraphLink `bson:"links" json:"links" default:"{}" validate:"required"` // Links is the list of links between elements in the graph
} }
func NewGraph() *Graph {
return &Graph{
Partial: false,
Zoom: 1,
Items: map[string]GraphItem{},
Links: []GraphLink{},
}
}
func (g *Graph) Clear(id string) { func (g *Graph) Clear(id string) {
realItems := map[string]GraphItem{} realItems := map[string]GraphItem{}
for k, it := range g.Items { for k, it := range g.Items {
@@ -38,6 +47,10 @@ func (wf *Graph) IsProcessing(item GraphItem) bool {
return item.Processing != nil return item.Processing != nil
} }
func (wf *Graph) IsNativeTool(item GraphItem) bool {
return item.NativeTool != nil
}
func (wf *Graph) IsCompute(item GraphItem) bool { func (wf *Graph) IsCompute(item GraphItem) bool {
return item.Compute != nil return item.Compute != nil
} }
@@ -55,7 +68,7 @@ func (wf *Graph) IsWorkflow(item GraphItem) bool {
} }
func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.ProcessingResource, resource resources.ResourceInterface, func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.ProcessingResource, resource resources.ResourceInterface,
f func(GraphItem) resources.ResourceInterface, request *tools.APIRequest) (float64, float64) { f func(GraphItem) resources.ResourceInterface, instance int, partnership int, buying int, strategy int, bookingMode int, request *tools.APIRequest) (float64, float64, error) {
nearestStart := float64(10000000000) nearestStart := float64(10000000000)
oneIsInfinite := false oneIsInfinite := false
longestDuration := float64(0) longestDuration := float64(0)
@@ -67,7 +80,10 @@ func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, proce
} else if link.Source.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the source is the processing and the destination is not a compute } else if link.Source.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the source is the processing and the destination is not a compute
source = link.Destination.ID source = link.Destination.ID
} }
priced := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, request) priced, err := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &instance, &partnership, &buying, &strategy, &bookingMode, request)
if err != nil {
return 0, 0, err
}
if source != "" { if source != "" {
if priced.GetLocationStart() != nil { if priced.GetLocationStart() != nil {
near := float64(priced.GetLocationStart().Sub(start).Seconds()) near := float64(priced.GetLocationStart().Sub(start).Seconds())
@@ -88,15 +104,16 @@ func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, proce
} }
} }
if oneIsInfinite { if oneIsInfinite {
return nearestStart, -1 return nearestStart, -1, nil
} }
return nearestStart, longestDuration return nearestStart, longestDuration, nil
} }
/* /*
* GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing * GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing
*/ */
func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string, request *tools.APIRequest) float64 { func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string,
instance int, partnership int, buying int, strategy int, bookingMode int, request *tools.APIRequest) (float64, error) {
currents := []float64{} // list of current time currents := []float64{} // list of current time
for _, link := range g.Links { // for each link for _, link := range g.Links { // for each link
var source string // source is the source of the link var source string // source is the source of the link
@@ -112,12 +129,19 @@ func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingI
if r == nil { // if item is nil, continue if r == nil { // if item is nil, continue
continue continue
} }
priced := r.ConvertToPricedResource(dt, request) priced, err := r.ConvertToPricedResource(dt, &instance, &partnership, &buying, &strategy, &bookingMode, request)
if err != nil {
return 0, err
}
current := priced.GetExplicitDurationInS() // get the explicit duration of the item current := priced.GetExplicitDurationInS() // get the explicit duration of the item
if current < 0 { // if current is negative, its means that duration of a before could be infinite continue if current < 0 { // if current is negative, its means that duration of a before could be infinite continue
return current return current, nil
} }
current += g.GetAverageTimeProcessingBeforeStart(current, source, request) // get the average time before start of the source add, err := g.GetAverageTimeProcessingBeforeStart(current, source, instance, partnership, buying, strategy, bookingMode, request)
if err != nil {
return 0, err
}
current += add // get the average time before start of the source
currents = append(currents, current) // append the current to the currents currents = append(currents, current) // append the current to the currents
} }
var max float64 // get the max time to wait dependancies to finish var max float64 // get the max time to wait dependancies to finish
@@ -126,12 +150,14 @@ func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingI
max = current max = current
} }
} }
return max return max, nil
} }
func (g *Graph) GetResource(id string) (tools.DataType, resources.ResourceInterface) { func (g *Graph) GetResource(id string) (tools.DataType, resources.ResourceInterface) {
if item, ok := g.Items[id]; ok { if item, ok := g.Items[id]; ok {
if item.Data != nil { if item.Data != nil {
return tools.NATIVE_TOOL, item.NativeTool
} else if item.Data != nil {
return tools.DATA_RESOURCE, item.Data return tools.DATA_RESOURCE, item.Data
} else if item.Compute != nil { } else if item.Compute != nil {
return tools.COMPUTE_RESOURCE, item.Compute return tools.COMPUTE_RESOURCE, item.Compute

View File

@@ -1,9 +1,15 @@
package workflow package workflow
import ( import (
"bufio"
"encoding/json"
"errors" "errors"
"fmt"
"mime/multipart"
"strings"
"time" "time"
"cloud.o-forge.io/core/oc-lib/models/booking"
"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area" "cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
"cloud.o-forge.io/core/oc-lib/models/common" "cloud.o-forge.io/core/oc-lib/models/common"
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
@@ -12,8 +18,19 @@ import (
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow/graph" "cloud.o-forge.io/core/oc-lib/models/workflow/graph"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
) )
type ConfigItem map[string]int
func (c ConfigItem) Get(key string) *int {
i := 0
if ins, ok := c[key]; ok {
i = ins
}
return &i
}
/* /*
* Workflow is a struct that represents a workflow * Workflow is a struct that represents a workflow
* it defines the native workflow * it defines the native workflow
@@ -31,6 +48,283 @@ func (d *Workflow) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) // Create a new instance of the accessor return NewAccessor(request) // Create a new instance of the accessor
} }
func (d *Workflow) GetResources(dt tools.DataType) []resources.ResourceInterface {
itf := []resources.ResourceInterface{}
switch dt {
case tools.NATIVE_TOOL:
for _, d := range d.NativeTools {
itf = append(itf, d)
}
return itf
case tools.DATA_RESOURCE:
for _, d := range d.DataResources {
itf = append(itf, d)
}
return itf
case tools.PROCESSING_RESOURCE:
for _, d := range d.ProcessingResources {
itf = append(itf, d)
}
return itf
case tools.COMPUTE_RESOURCE:
for _, d := range d.ComputeResources {
itf = append(itf, d)
}
return itf
case tools.WORKFLOW_RESOURCE:
for _, d := range d.WorkflowResources {
itf = append(itf, d)
}
return itf
case tools.STORAGE_RESOURCE:
for _, d := range d.StorageResources {
itf = append(itf, d)
}
return itf
}
return itf
}
func (d *Workflow) ExtractFromPlantUML(plantUML multipart.File, request *tools.APIRequest) (*Workflow, error) {
if plantUML == nil {
return d, errors.New("no file available to export")
}
defer plantUML.Close()
d.Datas = []string{}
d.Storages = []string{}
d.Processings = []string{}
d.Computes = []string{}
d.Workflows = []string{}
d.DataResources = []*resources.DataResource{}
d.StorageResources = []*resources.StorageResource{}
d.ProcessingResources = []*resources.ProcessingResource{}
d.ComputeResources = []*resources.ComputeResource{}
d.WorkflowResources = []*resources.WorkflowResource{}
d.Graph = graph.NewGraph()
resourceCatalog := map[string]func() resources.ResourceInterface{
"Processing": func() resources.ResourceInterface {
return &resources.ProcessingResource{
AbstractInstanciatedResource: resources.AbstractInstanciatedResource[*resources.ProcessingInstance]{
Instances: []*resources.ProcessingInstance{},
},
}
},
"Storage": func() resources.ResourceInterface {
return &resources.StorageResource{
AbstractInstanciatedResource: resources.AbstractInstanciatedResource[*resources.StorageResourceInstance]{
Instances: []*resources.StorageResourceInstance{},
},
}
},
"Data": func() resources.ResourceInterface {
return &resources.DataResource{
AbstractInstanciatedResource: resources.AbstractInstanciatedResource[*resources.DataInstance]{
Instances: []*resources.DataInstance{},
},
}
},
"ComputeUnit": func() resources.ResourceInterface {
return &resources.ComputeResource{
AbstractInstanciatedResource: resources.AbstractInstanciatedResource[*resources.ComputeResourceInstance]{
Instances: []*resources.ComputeResourceInstance{},
},
}
},
}
graphVarName := map[string]*graph.GraphItem{}
scanner := bufio.NewScanner(plantUML)
for scanner.Scan() {
line := scanner.Text()
for n, new := range resourceCatalog {
if strings.Contains(line, n+"(") && !strings.Contains(line, "!procedure") { // should exclude declaration of type.
newRes := new()
varName, graphItem, err := d.extractResourcePlantUML(line, newRes, n, request.PeerID)
if err != nil {
return d, err
}
graphVarName[varName] = graphItem
continue
} else if strings.Contains(line, n+"-->") {
err := d.extractLink(line, graphVarName, "-->", false)
if err != nil {
fmt.Println(err)
continue
}
} else if strings.Contains(line, n+"<--") {
err := d.extractLink(line, graphVarName, "<--", true)
if err != nil {
fmt.Println(err)
continue
}
} else if strings.Contains(line, n+"--") {
err := d.extractLink(line, graphVarName, "--", false)
if err != nil {
fmt.Println(err)
continue
}
} else if strings.Contains(line, n+"-") {
err := d.extractLink(line, graphVarName, "-", false)
if err != nil {
fmt.Println(err)
continue
}
}
}
}
if err := scanner.Err(); err != nil {
return d, err
}
d.generateResource(d.GetResources(tools.DATA_RESOURCE), request)
d.generateResource(d.GetResources(tools.PROCESSING_RESOURCE), request)
d.generateResource(d.GetResources(tools.STORAGE_RESOURCE), request)
d.generateResource(d.GetResources(tools.COMPUTE_RESOURCE), request)
d.generateResource(d.GetResources(tools.WORKFLOW_RESOURCE), request)
return d, nil
}
func (d *Workflow) generateResource(datas []resources.ResourceInterface, request *tools.APIRequest) error {
for _, d := range datas {
access := d.GetAccessor(request)
if _, code, err := access.LoadOne(d.GetID()); err != nil && code == 200 {
continue
}
access.StoreOne(d)
}
return nil
}
func (d *Workflow) extractLink(line string, graphVarName map[string]*graph.GraphItem, pattern string, reverse bool) error {
splitted := strings.Split(line, pattern)
if len(splitted) < 2 {
return errors.New("links elements not found")
}
if graphVarName[splitted[0]] != nil {
return errors.New("links elements not found -> " + strings.Trim(splitted[0], " "))
}
if graphVarName[splitted[1]] != nil {
return errors.New("links elements not found -> " + strings.Trim(splitted[1], " "))
}
link := &graph.GraphLink{
Source: graph.Position{
ID: graphVarName[splitted[0]].ID,
X: 0,
Y: 0,
},
Destination: graph.Position{
ID: graphVarName[splitted[1]].ID,
X: 0,
Y: 0,
},
}
if reverse {
tmp := link.Destination
link.Destination = link.Source
link.Source = tmp
}
splittedComments := strings.Split(line, "'")
if len(splittedComments) <= 1 {
return errors.New("Can't deserialize Object, there's no commentary")
}
comment := strings.ReplaceAll(splittedComments[1], "'", "") // for now it's a json.
json.Unmarshal([]byte(comment), link)
d.Graph.Links = append(d.Graph.Links, *link)
return nil
}
func (d *Workflow) extractResourcePlantUML(line string, resource resources.ResourceInterface, dataName string, peerID string) (string, *graph.GraphItem, error) {
splittedFunc := strings.Split(line, "(")
if len(splittedFunc) <= 1 {
return "", nil, errors.New("Can't deserialize Object, there's no func")
}
splittedParams := strings.Split(splittedFunc[1], ",")
if len(splittedFunc) <= 1 {
return "", nil, errors.New("Can't deserialize Object, there's no params")
}
varName := splittedParams[0]
splitted := strings.Split(splittedParams[1], "\"")
if len(splitted) <= 1 {
return "", nil, errors.New("Can't deserialize Object, there's no name")
}
resource.SetName(splitted[1])
splittedComments := strings.Split(line, "'")
if len(splittedComments) <= 1 {
return "", nil, errors.New("Can't deserialize Object, there's no commentary")
}
comment := strings.ReplaceAll(splittedComments[1], "'", "") // for now it's a json.
instance := d.getNewInstance(dataName, splitted[1], peerID)
if instance == nil {
return "", nil, errors.New("No instance found.")
}
resource.AddInstances(instance)
json.Unmarshal([]byte(comment), instance)
// deserializer les instances... une instance doit par défaut avoir certaines valeurs d'accès.
graphID := uuid.New()
graphItem := &graph.GraphItem{
ID: graphID.String(),
}
graphItem = d.getNewGraphItem(dataName, graphItem, resource)
d.Graph.Items[graphID.String()] = *graphItem
return varName, graphItem, nil
}
func (d *Workflow) getNewGraphItem(dataName string, graphItem *graph.GraphItem, resource resources.ResourceInterface) *graph.GraphItem {
switch dataName {
case "Data":
d.Datas = append(d.Datas, resource.GetID())
d.DataResources = append(d.DataResources, resource.(*resources.DataResource))
graphItem.Data = resource.(*resources.DataResource)
case "Processing":
d.Processings = append(d.Processings, resource.GetID())
d.ProcessingResources = append(d.ProcessingResources, resource.(*resources.ProcessingResource))
graphItem.Processing = resource.(*resources.ProcessingResource)
case "Event":
access := resources.NewAccessor[*resources.NativeTool](tools.NATIVE_TOOL, &tools.APIRequest{
Admin: true,
}, func() utils.DBObject { return &resources.NativeTool{} })
t, _, err := access.Search(nil, "WORKFLOW_EVENT", false)
if err == nil && len(t) > 0 {
d.NativeTool = append(d.NativeTool, t[0].GetID())
graphItem.NativeTool = t[0].(*resources.NativeTool)
}
case "Storage":
d.Storages = append(d.Storages, resource.GetID())
d.StorageResources = append(d.StorageResources, resource.(*resources.StorageResource))
graphItem.Storage = resource.(*resources.StorageResource)
case "ComputeUnit":
d.Computes = append(d.Computes, resource.GetID())
d.ComputeResources = append(d.ComputeResources, resource.(*resources.ComputeResource))
graphItem.Compute = resource.(*resources.ComputeResource)
default:
return graphItem
}
return graphItem
}
func (d *Workflow) getNewInstance(dataName string, name string, peerID string) resources.ResourceInstanceITF {
switch dataName {
case "Data":
return resources.NewDataInstance(name, peerID)
case "Processing":
return resources.NewProcessingInstance(name, peerID)
case "Storage":
return resources.NewStorageResourceInstance(name, peerID)
case "ComputeUnit":
return resources.NewComputeResourceInstance(name, peerID)
default:
return nil
}
}
type Deps struct { type Deps struct {
Source string Source string
Dest string Dest string
@@ -54,6 +348,12 @@ func (w *Workflow) IsDependancy(id string) []Deps {
return dependancyOfIDs return dependancyOfIDs
} }
func (w *Workflow) GetFirstItems() []graph.GraphItem {
return w.GetGraphItems(func(item graph.GraphItem) bool {
return len(w.GetDependencies(w.GetID())) == 0
})
}
func (w *Workflow) GetDependencies(id string) (dependencies []Deps) { func (w *Workflow) GetDependencies(id string) (dependencies []Deps) {
for _, link := range w.Graph.Links { for _, link := range w.Graph.Links {
if _, ok := w.Graph.Items[link.Source.ID]; !ok { if _, ok := w.Graph.Items[link.Source.ID]; !ok {
@@ -78,16 +378,26 @@ func (w *Workflow) GetGraphItems(f func(item graph.GraphItem) bool) (list_datas
} }
func (w *Workflow) GetPricedItem( func (w *Workflow) GetPricedItem(
f func(item graph.GraphItem) bool, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) map[string]pricing.PricedItemITF { f func(item graph.GraphItem) bool, request *tools.APIRequest,
instance int,
partnership int,
buying int,
strategy int,
bookingMode int,
buyingStrategy int,
pricingStrategy int) (map[string]pricing.PricedItemITF, error) {
list_datas := map[string]pricing.PricedItemITF{} list_datas := map[string]pricing.PricedItemITF{}
for _, item := range w.Graph.Items { for _, item := range w.Graph.Items {
if f(item) { if f(item) {
dt, res := item.GetResource() dt, res := item.GetResource()
ord := res.ConvertToPricedResource(dt, request) ord, err := res.ConvertToPricedResource(dt, &instance, &partnership, &buying, &strategy, &bookingMode, request)
if err != nil {
return list_datas, err
}
list_datas[res.GetID()] = ord list_datas[res.GetID()] = ord
} }
} }
return list_datas return list_datas, nil
} }
type Related struct { type Related struct {
@@ -110,19 +420,17 @@ func (w *Workflow) GetByRelatedProcessing(processingID string, g func(item graph
_, node = item.GetResource() // we are looking for the storage as destination _, node = item.GetResource() // we are looking for the storage as destination
} }
if processingID == nodeID && node != nil { // if the storage is linked to the processing if processingID == nodeID && node != nil { // if the storage is linked to the processing
if _, ok := related[processingID]; !ok { relID := node.GetID()
related[processingID] = Related{} rel := Related{}
}
rel := related[node.GetID()]
rel.Node = node rel.Node = node
rel.Links = append(rel.Links, link) rel.Links = append(rel.Links, link)
related[processingID] = rel related[relID] = rel
} }
} }
return related return related
} }
func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool { func (ao *Workflow) VerifyAuth(callName string, request *tools.APIRequest) bool {
isAuthorized := false isAuthorized := false
if len(ao.Shared) > 0 { if len(ao.Shared) > 0 {
for _, shared := range ao.Shared { for _, shared := range ao.Shared {
@@ -130,11 +438,11 @@ func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool {
if code != 200 || shared == nil { if code != 200 || shared == nil {
isAuthorized = false isAuthorized = false
} else { } else {
isAuthorized = shared.VerifyAuth(request) isAuthorized = shared.VerifyAuth(callName, request)
} }
} }
} }
return ao.AbstractObject.VerifyAuth(request) || isAuthorized return ao.AbstractObject.VerifyAuth(callName, request) || isAuthorized
} }
/* /*
@@ -166,70 +474,140 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) {
return true, nil return true, nil
} }
func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIRequest) (float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) { func (wf *Workflow) Planify(start time.Time, end *time.Time, instances ConfigItem, partnerships ConfigItem, buyings ConfigItem, strategies ConfigItem, bookingMode int, request *tools.APIRequest) (bool, float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) {
priceds := map[tools.DataType]map[string]pricing.PricedItemITF{} priceds := map[tools.DataType]map[string]pricing.PricedItemITF{}
ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request, wf.Graph.IsProcessing, ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request, wf.Graph.IsProcessing,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request)) * time.Second), priced.GetExplicitDurationInS() d, err := wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(),
}, func(started time.Time, duration float64) *time.Time { *instances.Get(res.GetID()), *partnerships.Get(res.GetID()), *buyings.Get(res.GetID()), *strategies.Get(res.GetID()),
bookingMode, request)
if err != nil {
return start, 0, err
}
return start.Add(time.Duration(d) * time.Second), priced.GetExplicitDurationInS(), nil
}, func(started time.Time, duration float64) (*time.Time, error) {
s := started.Add(time.Duration(duration)) s := started.Add(time.Duration(duration))
return &s return &s, nil
}) })
if err != nil { if err != nil {
return 0, priceds, nil, err return false, 0, priceds, nil, err
} }
if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, wf, priceds, request, if _, priceds, err = plan[resources.ResourceInterface](tools.NATIVE_TOOL, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request,
wf.Graph.IsData, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { wf.Graph.IsNativeTool, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
return start, 0 return start, 0, nil
}, func(started time.Time, duration float64) *time.Time { }, func(started time.Time, duration float64) (*time.Time, error) {
return end return end, nil
}); err != nil { }); err != nil {
return 0, priceds, nil, err return false, 0, priceds, nil, err
} }
for k, f := range map[tools.DataType]func(graph.GraphItem) bool{tools.STORAGE_RESOURCE: wf.Graph.IsStorage, tools.COMPUTE_RESOURCE: wf.Graph.IsCompute} { if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request,
if _, priceds, err = plan[resources.ResourceInterface](k, wf, priceds, request, wf.Graph.IsData, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
f, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { return start, 0, nil
nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) { }, func(started time.Time, duration float64) (*time.Time, error) {
return end, nil
}); err != nil {
return false, 0, priceds, nil, err
}
for k, f := range map[tools.DataType]func(graph.GraphItem) bool{tools.STORAGE_RESOURCE: wf.Graph.IsStorage,
tools.COMPUTE_RESOURCE: wf.Graph.IsCompute} {
if _, priceds, err = plan[resources.ResourceInterface](k, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request,
f, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
nearestStart, longestDuration, err := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) {
if f(i) { if f(i) {
_, r = i.GetResource() _, r = i.GetResource()
} }
return r return r
}, request) }, *instances.Get(res.GetID()), *partnerships.Get(res.GetID()),
return start.Add(time.Duration(nearestStart) * time.Second), longestDuration *buyings.Get(res.GetID()), *strategies.Get(res.GetID()), bookingMode, request)
}, func(started time.Time, duration float64) *time.Time { if err != nil {
return start, longestDuration, err
}
return start.Add(time.Duration(nearestStart) * time.Second), longestDuration, nil
}, func(started time.Time, duration float64) (*time.Time, error) {
s := started.Add(time.Duration(duration)) s := started.Add(time.Duration(duration))
return &s return &s, nil
}); err != nil { }); err != nil {
return 0, priceds, nil, err return false, 0, priceds, nil, err
} }
} }
longest := common.GetPlannerLongestTime(end, priceds, request) longest := common.GetPlannerLongestTime(end, priceds, request)
if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, wf, priceds, request, wf.Graph.IsWorkflow, if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, instances, partnerships, buyings, strategies,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { bookingMode, wf, priceds, request, wf.Graph.IsWorkflow,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
start := start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second) start := start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second)
longest := float64(-1) longest := float64(-1)
r, code, err := res.GetAccessor(request).LoadOne(res.GetID()) r, code, err := res.GetAccessor(request).LoadOne(res.GetID())
if code != 200 || err != nil { if code != 200 || err != nil {
return start, longest return start, longest, err
} }
if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request); err != nil { _, neoLongest, priceds2, _, err := r.(*Workflow).Planify(start, end, instances, partnerships, buyings, strategies, bookingMode, request)
return start, longest // should ... import priced
if err != nil {
return start, longest, err
} else if neoLongest > longest { } else if neoLongest > longest {
longest = neoLongest longest = neoLongest
} }
return start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second), longest for k, v := range priceds2 {
}, func(start time.Time, longest float64) *time.Time { if priceds[k] == nil {
s := start.Add(time.Duration(longest) * time.Second) priceds[k] = map[string]pricing.PricedItemITF{}
return &s
}); err != nil {
return 0, priceds, nil, err
} }
return longest, priceds, wf, nil for k2, v2 := range v {
if priceds[k][k2] != nil {
v2.AddQuantity(priceds[k][k2].GetQuantity())
}
}
}
return start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second), longest, nil
}, func(start time.Time, longest float64) (*time.Time, error) {
s := start.Add(time.Duration(longest) * time.Second)
return &s, nil
}); err != nil {
return false, 0, priceds, nil, err
}
isPreemptible := true
for _, first := range wf.GetFirstItems() {
_, res := first.GetResource()
if res.GetBookingModes()[booking.PREEMPTED] == nil {
isPreemptible = false
break
}
}
return isPreemptible, longest, priceds, wf, nil
}
// Returns a map of DataType (processing,computing,data,storage,worfklow) where each resource (identified by its UUID)
// is mapped to the list of its items (different appearance) in the graph
// ex: if the same Minio storage is represented by several nodes in the graph, in [tools.STORAGE_RESSOURCE] its UUID will be mapped to
// the list of GraphItem ID that correspond to the ID of each node
func (w *Workflow) GetItemsByResources() map[tools.DataType]map[string][]string {
res := make(map[tools.DataType]map[string][]string)
dtMethodMap := map[tools.DataType]func() []graph.GraphItem{
tools.STORAGE_RESOURCE: func() []graph.GraphItem { return w.GetGraphItems(w.Graph.IsStorage) },
tools.DATA_RESOURCE: func() []graph.GraphItem { return w.GetGraphItems(w.Graph.IsData) },
tools.COMPUTE_RESOURCE: func() []graph.GraphItem { return w.GetGraphItems(w.Graph.IsCompute) },
tools.PROCESSING_RESOURCE: func() []graph.GraphItem { return w.GetGraphItems(w.Graph.IsProcessing) },
tools.WORKFLOW_RESOURCE: func() []graph.GraphItem { return w.GetGraphItems(w.Graph.IsWorkflow) },
}
for dt, meth := range dtMethodMap {
res[dt] = make(map[string][]string)
items := meth()
for _, i := range items {
_, r := i.GetResource()
rId := r.GetID()
res[dt][rId] = append(res[dt][rId], i.ID)
}
}
return res
} }
func plan[T resources.ResourceInterface]( func plan[T resources.ResourceInterface](
dt tools.DataType, wf *Workflow, priceds map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest, dt tools.DataType, instances ConfigItem, partnerships ConfigItem, buyings ConfigItem, strategies ConfigItem, bookingMode int, wf *Workflow, priceds map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest,
f func(graph.GraphItem) bool, start func(resources.ResourceInterface, pricing.PricedItemITF) (time.Time, float64), end func(time.Time, float64) *time.Time) ([]T, map[tools.DataType]map[string]pricing.PricedItemITF, error) { f func(graph.GraphItem) bool,
start func(resources.ResourceInterface, pricing.PricedItemITF) (time.Time, float64, error),
end func(time.Time, float64) (*time.Time, error)) ([]T, map[tools.DataType]map[string]pricing.PricedItemITF, error) {
resources := []T{} resources := []T{}
for _, item := range wf.GetGraphItems(f) { for _, item := range wf.GetGraphItems(f) {
if priceds[dt] == nil { if priceds[dt] == nil {
@@ -239,23 +617,34 @@ func plan[T resources.ResourceInterface](
if realItem == nil { if realItem == nil {
return resources, priceds, errors.New("could not load the processing resource") return resources, priceds, errors.New("could not load the processing resource")
} }
priced := realItem.ConvertToPricedResource(dt, request) priced, err := realItem.ConvertToPricedResource(dt, instances.Get(realItem.GetID()),
partnerships.Get(realItem.GetID()), buyings.Get(realItem.GetID()), strategies.Get(realItem.GetID()), &bookingMode, request)
if err != nil {
return resources, priceds, err
}
// Should be commented once the Pricing selection feature has been implemented, related to the commit d35ad440fa77763ec7f49ab34a85e47e75581b61 // Should be commented once the Pricing selection feature has been implemented, related to the commit d35ad440fa77763ec7f49ab34a85e47e75581b61
// if priced.SelectPricing() == nil { // if priced.SelectPricing() == nil {
// return resources, priceds, errors.New("no pricings are selected... can't proceed") // return resources, priceds, errors.New("no pricings are selected... can't proceed")
// } // }
started, duration := start(realItem, priced) started, duration, err := start(realItem, priced)
if err != nil {
return resources, priceds, err
}
priced.SetLocationStart(started) priced.SetLocationStart(started)
if duration >= 0 { if duration >= 0 {
if e := end(started, duration); e != nil { if e, err := end(started, duration); err == nil && e != nil {
priced.SetLocationEnd(*e) priced.SetLocationEnd(*e)
} }
} }
if e := end(started, priced.GetExplicitDurationInS()); e != nil { if e, err := end(started, priced.GetExplicitDurationInS()); err != nil && e != nil {
priced.SetLocationEnd(*e) priced.SetLocationEnd(*e)
} }
resources = append(resources, realItem.(T)) resources = append(resources, realItem.(T))
if priceds[dt][item.ID] != nil {
priced.AddQuantity(priceds[dt][item.ID].GetQuantity())
}
priceds[dt][item.ID] = priced priceds[dt][item.ID] = priced
} }
return resources, priceds, nil return resources, priceds, nil
} }

View File

@@ -68,7 +68,9 @@ func (a *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *t
paccess := &peer.Peer{} paccess := &peer.Peer{}
for _, p := range res.(*shallow_collaborative_area.ShallowCollaborativeArea).Peers { for _, p := range res.(*shallow_collaborative_area.ShallowCollaborativeArea).Peers {
paccess.UUID = p paccess.UUID = p
if ok, _ := paccess.IsMySelf(); ok { // if the peer is the current peer, never share because it will create a loop if ok, _ := utils.IsMySelf(p, paccess.GetAccessor(&tools.APIRequest{
Admin: true,
})); ok { // if the peer is the current peer, never share because it will create a loop
continue continue
} }
if delete { // if the workflow is deleted, share the deletion orderResourceAccessor utils.Accessor if delete { // if the workflow is deleted, share the deletion orderResourceAccessor utils.Accessor

View File

@@ -1,149 +0,0 @@
package workflow_execution_test
import (
"testing"
"time"
"cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockAccessor struct {
mock.Mock
}
func (m *MockAccessor) LoadOne(id string) (interface{}, int, error) {
args := m.Called(id)
return args.Get(0), args.Int(1), args.Error(2)
}
func TestNewScheduler_ValidInput(t *testing.T) {
s := "2025-06-16T15:00:00"
e := "2025-06-16T17:00:00"
dur := 7200.0
cronStr := "0 0 * * * *"
sched := workflow_execution.NewScheduler(s, e, dur, cronStr)
assert.NotNil(t, sched)
assert.Equal(t, dur, sched.DurationS)
assert.Equal(t, cronStr, sched.Cron)
}
func TestNewScheduler_InvalidStart(t *testing.T) {
s := "invalid"
e := "2025-06-16T17:00:00"
dur := 7200.0
cronStr := "0 0 * * * *"
sched := workflow_execution.NewScheduler(s, e, dur, cronStr)
assert.Nil(t, sched)
}
func TestNewScheduler_InvalidEnd(t *testing.T) {
s := "2025-06-16T15:00:00"
e := "invalid"
dur := 7200.0
cronStr := "0 0 * * * *"
sched := workflow_execution.NewScheduler(s, e, dur, cronStr)
assert.NotNil(t, sched)
assert.Nil(t, sched.End)
}
func TestGetDates_NoCron(t *testing.T) {
start := time.Now()
end := start.Add(2 * time.Hour)
s := &workflow_execution.WorkflowSchedule{
Start: start,
End: &end,
}
schedule, err := s.GetDates()
assert.NoError(t, err)
assert.Len(t, schedule, 1)
assert.Equal(t, start, schedule[0].Start)
assert.Equal(t, end, *schedule[0].End)
}
func TestGetDates_InvalidCron(t *testing.T) {
start := time.Now()
end := start.Add(2 * time.Hour)
s := &workflow_execution.WorkflowSchedule{
Start: start,
End: &end,
Cron: "bad cron",
}
_, err := s.GetDates()
assert.Error(t, err)
}
func TestGetDates_ValidCron(t *testing.T) {
start := time.Now()
end := start.Add(10 * time.Minute)
s := &workflow_execution.WorkflowSchedule{
Start: start,
End: &end,
DurationS: 60,
Cron: "0 */2 * * * *",
}
dates, err := s.GetDates()
assert.NoError(t, err)
assert.Greater(t, len(dates), 0)
}
func TestGetExecutions_Success(t *testing.T) {
start := time.Now()
end := start.Add(1 * time.Hour)
ws := &workflow_execution.WorkflowSchedule{
UUID: uuid.New().String(),
Start: start,
End: &end,
}
wf := &workflow.Workflow{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: "TestWorkflow",
},
}
execs, err := ws.GetExecutions(wf)
assert.NoError(t, err)
assert.Greater(t, len(execs), 0)
assert.Equal(t, wf.UUID, execs[0].WorkflowID)
assert.Equal(t, ws.UUID, execs[0].ExecutionsID)
assert.Equal(t, enum.DRAFT, execs[0].State)
}
func TestSchedules_NoRequest(t *testing.T) {
ws := &workflow_execution.WorkflowSchedule{}
ws, wf, execs, err := ws.Schedules("someID", nil)
assert.Error(t, err)
assert.Nil(t, wf)
assert.Len(t, execs, 0)
assert.Equal(t, ws, ws)
}
// Additional test stubs to be completed with gomock usage for:
// - CheckBooking
// - BookExecs
// - getBooking
// - Schedules (success path)
// - Planify mocking in CheckBooking
// - Peer interaction in BookExecs
// - Caller deep copy errors in getCallerCopy
// Will be continued...

View File

@@ -9,8 +9,18 @@ import (
"cloud.o-forge.io/core/oc-lib/models/workflow_execution" "cloud.o-forge.io/core/oc-lib/models/workflow_execution"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
) )
type MockAccessor struct {
mock.Mock
}
func (m *MockAccessor) LoadOne(id string) (interface{}, int, error) {
args := m.Called(id)
return args.Get(0), args.Int(1), args.Error(2)
}
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) { func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
args := m.Called(id) args := m.Called(id)
return nil, args.Int(1), args.Error(2) return nil, args.Int(1), args.Error(2)
@@ -93,7 +103,7 @@ func TestGetName_ReturnsCorrectFormat(t *testing.T) {
func TestVerifyAuth_AlwaysTrue(t *testing.T) { func TestVerifyAuth_AlwaysTrue(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{} exec := &workflow_execution.WorkflowExecution{}
assert.True(t, exec.VerifyAuth(nil)) assert.True(t, exec.VerifyAuth("get", nil))
} }
func TestUpdateOne_RejectsZeroState(t *testing.T) { func TestUpdateOne_RejectsZeroState(t *testing.T) {

View File

@@ -1,6 +1,7 @@
package workflow_execution package workflow_execution
import ( import (
"encoding/json"
"strings" "strings"
"time" "time"
@@ -10,6 +11,7 @@ import (
"cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource" "cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid" "github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
@@ -22,6 +24,7 @@ import (
*/ */
type WorkflowExecution struct { type WorkflowExecution struct {
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
Priority int `json:"priority" bson:"priority"` // will best effort on default // Will Best Effort on priority
PeerBuyByGraph map[string]map[string][]string `json:"peer_buy_by_graph,omitempty" bson:"peer_buy_by_graph,omitempty"` // BookByResource is a map of the resource id and the list of the booking id PeerBuyByGraph map[string]map[string][]string `json:"peer_buy_by_graph,omitempty" bson:"peer_buy_by_graph,omitempty"` // BookByResource is a map of the resource id and the list of the booking id
PeerBookByGraph map[string]map[string][]string `json:"peer_book_by_graph,omitempty" bson:"peer_book_by_graph,omitempty"` // BookByResource is a map of the resource id and the list of the booking id PeerBookByGraph map[string]map[string][]string `json:"peer_book_by_graph,omitempty" bson:"peer_book_by_graph,omitempty"` // BookByResource is a map of the resource id and the list of the booking id
ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty"` ExecutionsID string `json:"executions_id,omitempty" bson:"executions_id,omitempty"`
@@ -29,6 +32,11 @@ type WorkflowExecution struct {
EndDate *time.Time `json:"end_date,omitempty" bson:"end_date,omitempty"` // EndDate is the end date of the workflow EndDate *time.Time `json:"end_date,omitempty" bson:"end_date,omitempty"` // EndDate is the end date of the workflow
State enum.BookingStatus `json:"state" bson:"state" default:"0"` // TEMPORARY TODO DEFAULT 1 -> 0 State is the state of the workflow State enum.BookingStatus `json:"state" bson:"state" default:"0"` // TEMPORARY TODO DEFAULT 1 -> 0 State is the state of the workflow
WorkflowID string `json:"workflow_id" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow WorkflowID string `json:"workflow_id" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow
SelectedInstances workflow.ConfigItem `json:"selected_instances"`
SelectedPartnerships workflow.ConfigItem `json:"selected_partnerships"`
SelectedBuyings workflow.ConfigItem `json:"selected_buyings"`
SelectedStrategies workflow.ConfigItem `json:"selected_strategies"`
} }
func (r *WorkflowExecution) StoreDraftDefault() { func (r *WorkflowExecution) StoreDraftDefault() {
@@ -107,7 +115,7 @@ func (d *WorkflowExecution) GetAccessor(request *tools.APIRequest) utils.Accesso
return NewAccessor(request) // Create a new instance of the accessor return NewAccessor(request) // Create a new instance of the accessor
} }
func (d *WorkflowExecution) VerifyAuth(request *tools.APIRequest) bool { func (d *WorkflowExecution) VerifyAuth(callName string, request *tools.APIRequest) bool {
return true return true
} }
@@ -141,13 +149,16 @@ func (d *WorkflowExecution) buyEach(bs pricing.BillingStrategy, executionsID str
if s := priced.GetLocationStart(); s != nil { if s := priced.GetLocationStart(); s != nil {
start = *s start = *s
} }
var m map[string]interface{}
b, _ := json.Marshal(priced)
json.Unmarshal(b, &m)
end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second) end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second)
bookingItem := &purchase_resource.PurchaseResource{ bookingItem := &purchase_resource.PurchaseResource{
AbstractObject: utils.AbstractObject{ AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(), UUID: uuid.New().String(),
Name: d.GetName() + "_" + executionsID + "_" + wfID, Name: d.GetName() + "_" + executionsID + "_" + wfID,
}, },
PricedItem: priced, PricedItem: m,
ExecutionsID: executionsID, ExecutionsID: executionsID,
DestPeerID: priced.GetCreatorID(), DestPeerID: priced.GetCreatorID(),
ResourceID: priced.GetID(), ResourceID: priced.GetID(),
@@ -189,12 +200,15 @@ func (d *WorkflowExecution) bookEach(executionsID string, wfID string, dt tools.
start = *s start = *s
} }
end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second) end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second)
var m map[string]interface{}
b, _ := json.Marshal(priced)
json.Unmarshal(b, &m)
bookingItem := &booking.Booking{ bookingItem := &booking.Booking{
AbstractObject: utils.AbstractObject{ AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(), UUID: uuid.New().String(),
Name: d.GetName() + "_" + executionsID + "_" + wfID, Name: d.GetName() + "_" + executionsID + "_" + wfID,
}, },
PricedItem: priced, PricedItem: m,
ExecutionsID: executionsID, ExecutionsID: executionsID,
State: enum.SCHEDULED, State: enum.SCHEDULED,
ResourceID: priced.GetID(), ResourceID: priced.GetID(),

View File

@@ -1,321 +0,0 @@
package workflow_execution
import (
"errors"
"fmt"
"strings"
"sync"
"time"
"cloud.o-forge.io/core/oc-lib/models/bill"
"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/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/order"
"cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
"github.com/robfig/cron"
)
/*
* WorkflowSchedule is a struct that contains the scheduling information of a workflow
* It contains the mode of the schedule (Task or Service), the name of the schedule, the start and end time of the schedule and the cron expression
*/
// it's a flying object only use in a session time. It's not stored in the database
type WorkflowSchedule struct {
UUID string `json:"id" validate:"required"` // ExecutionsID is the list of the executions id of the workflow
Workflow *workflow.Workflow `json:"workflow,omitempty"` // Workflow is the workflow dependancy of the schedule
WorkflowExecution []*WorkflowExecution `json:"workflow_executions,omitempty"` // WorkflowExecution is the list of executions of the workflow
Message string `json:"message,omitempty"` // Message is the message of the schedule
Warning string `json:"warning,omitempty"` // Warning is the warning message of the schedule
Start time.Time `json:"start" validate:"required,ltfield=End"` // Start is the start time of the schedule, is required and must be less than the End time
End *time.Time `json:"end,omitempty"` // End is the end time of the schedule, is required and must be greater than the Start time
DurationS float64 `json:"duration_s" default:"-1"` // End is the end time of the schedule
Cron string `json:"cron,omitempty"` // here the cron format : ss mm hh dd MM dw task
SelectedBillingStrategy pricing.BillingStrategy `json:"selected_billing_strategy"`
}
func NewScheduler(start string, end string, durationInS float64, cron string) *WorkflowSchedule {
s, err := time.Parse("2006-01-02T15:04:05", start)
if err != nil {
return nil
}
ws := &WorkflowSchedule{
UUID: uuid.New().String(),
Start: s,
DurationS: durationInS,
Cron: cron,
}
e, err := time.Parse("2006-01-02T15:04:05", end)
if err == nil {
ws.End = &e
}
return ws
}
func (ws *WorkflowSchedule) GetBuyAndBook(wfID string, request *tools.APIRequest) (bool, *workflow.Workflow, []*WorkflowExecution, []*purchase_resource.PurchaseResource, []*booking.Booking, error) {
if request.Caller == nil && request.Caller.URLS == nil && request.Caller.URLS[tools.BOOKING] == nil || request.Caller.URLS[tools.BOOKING][tools.GET] == "" {
return false, nil, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, errors.New("no caller defined")
}
access := workflow.NewAccessor(request)
res, code, err := access.LoadOne(wfID)
if code != 200 {
return false, nil, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, errors.New("could not load the workflow with id: " + err.Error())
}
wf := res.(*workflow.Workflow)
longest, priceds, wf, err := wf.Planify(ws.Start, ws.End, request)
if err != nil {
return false, wf, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, err
}
ws.DurationS = longest
ws.Message = "We estimate that the workflow will start at " + ws.Start.String() + " and last " + fmt.Sprintf("%v", ws.DurationS) + " seconds."
if ws.End != nil && ws.Start.Add(time.Duration(longest)*time.Second).After(*ws.End) {
ws.Warning = "The workflow may be too long to be executed in the given time frame, we will try to book it anyway\n"
}
execs, err := ws.GetExecutions(wf)
if err != nil {
return false, wf, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, err
}
purchased := []*purchase_resource.PurchaseResource{}
bookings := []*booking.Booking{}
for _, exec := range execs {
purchased = append(purchased, exec.Buy(ws.SelectedBillingStrategy, ws.UUID, wfID, priceds)...)
bookings = append(bookings, exec.Book(ws.UUID, wfID, priceds)...)
}
errCh := make(chan error, len(bookings))
var m sync.Mutex
for _, b := range bookings {
go getBooking(b, request, errCh, &m)
}
for i := 0; i < len(bookings); i++ {
if err := <-errCh; err != nil {
return false, wf, execs, purchased, bookings, err
}
}
return true, wf, execs, purchased, bookings, nil
}
func (ws *WorkflowSchedule) GenerateOrder(purchases []*purchase_resource.PurchaseResource, bookings []*booking.Booking, request *tools.APIRequest) error {
newOrder := &order.Order{
AbstractObject: utils.AbstractObject{
Name: "order_" + request.PeerID + "_" + time.Now().UTC().Format("2006-01-02T15:04:05"),
IsDraft: true,
},
ExecutionsID: ws.UUID,
Purchases: purchases,
Bookings: bookings,
Status: enum.PENDING,
}
if res, _, err := order.NewAccessor(request).StoreOne(newOrder); err == nil {
if _, err := bill.DraftFirstBill(res.(*order.Order), request); err != nil {
return err
}
return nil
} else {
return err
}
}
func getBooking(b *booking.Booking, request *tools.APIRequest, errCh chan error, m *sync.Mutex) {
m.Lock()
c, err := getCallerCopy(request, errCh)
if err != nil {
errCh <- err
return
}
m.Unlock()
meth := c.URLS[tools.BOOKING][tools.GET]
meth = strings.ReplaceAll(meth, ":id", b.ResourceID)
meth = strings.ReplaceAll(meth, ":start_date", b.ExpectedStartDate.Format("2006-01-02T15:04:05"))
meth = strings.ReplaceAll(meth, ":end_date", b.ExpectedEndDate.Format("2006-01-02T15:04:05"))
c.URLS[tools.BOOKING][tools.GET] = meth
_, err = (&peer.Peer{}).LaunchPeerExecution(b.DestPeerID, b.ResourceID, tools.BOOKING, tools.GET, nil, &c)
if err != nil {
errCh <- err
return
}
errCh <- nil
}
func getCallerCopy(request *tools.APIRequest, errCh chan error) (tools.HTTPCaller, error) {
var c tools.HTTPCaller
err := request.Caller.DeepCopy(c)
if err != nil {
errCh <- err
return tools.HTTPCaller{}, nil
}
c.URLS = request.Caller.URLS
return c, err
}
func (ws *WorkflowSchedule) Schedules(wfID string, request *tools.APIRequest) (*WorkflowSchedule, *workflow.Workflow, []*WorkflowExecution, error) {
if request == nil {
return ws, nil, []*WorkflowExecution{}, errors.New("no request found")
}
c := request.Caller
if c == nil || c.URLS == nil || c.URLS[tools.BOOKING] == nil {
return ws, nil, []*WorkflowExecution{}, errors.New("no caller defined")
}
methods := c.URLS[tools.BOOKING]
if _, ok := methods[tools.GET]; !ok {
return ws, nil, []*WorkflowExecution{}, errors.New("no path found")
}
ok, wf, executions, purchases, bookings, err := ws.GetBuyAndBook(wfID, request)
ws.WorkflowExecution = executions
if !ok || err != nil {
return ws, nil, executions, errors.New("could not book the workflow : " + fmt.Sprintf("%v", err))
}
ws.Workflow = wf
var errCh = make(chan error, len(bookings))
var m sync.Mutex
for _, purchase := range purchases {
go ws.CallDatacenter(purchase, purchase.DestPeerID, tools.PURCHASE_RESOURCE, request, errCh, &m)
}
for i := 0; i < len(purchases); i++ {
if err := <-errCh; err != nil {
return ws, wf, executions, errors.New("could not launch the peer execution : " + fmt.Sprintf("%v", err))
}
}
errCh = make(chan error, len(bookings))
for _, booking := range bookings {
go ws.CallDatacenter(booking, booking.DestPeerID, tools.BOOKING, request, errCh, &m)
}
for i := 0; i < len(bookings); i++ {
if err := <-errCh; err != nil {
return ws, wf, executions, errors.New("could not launch the peer execution : " + fmt.Sprintf("%v", err))
}
}
if err := ws.GenerateOrder(purchases, bookings, request); err != nil {
return ws, wf, executions, err
}
fmt.Println("Schedules")
for _, exec := range executions {
err := exec.PurgeDraft(request)
if err != nil {
return ws, nil, []*WorkflowExecution{}, errors.New("purge draft" + fmt.Sprintf("%v", err))
}
exec.StoreDraftDefault()
utils.GenericStoreOne(exec, NewAccessor(request))
}
fmt.Println("Schedules")
return ws, wf, executions, nil
}
func (ws *WorkflowSchedule) CallDatacenter(purchase utils.DBObject, destPeerID string, dt tools.DataType, request *tools.APIRequest, errCh chan error, m *sync.Mutex) {
m.Lock()
c, err := getCallerCopy(request, errCh)
if err != nil {
errCh <- err
return
}
m.Unlock()
if res, err := (&peer.Peer{}).LaunchPeerExecution(destPeerID, "", dt, tools.POST, purchase.Serialize(purchase), &c); err != nil {
errCh <- err
return
} else {
data := res["data"].(map[string]interface{})
purchase.SetID(fmt.Sprintf("%v", data["id"]))
}
errCh <- nil
}
/*
BOOKING IMPLIED TIME, not of subscription but of execution
so is processing time execution time applied on computes
data can improve the processing time
time should implied a security time border (10sec) if not from the same executions
VERIFY THAT WE HANDLE DIFFERENCE BETWEEN LOCATION TIME && BOOKING
*/
/*
* getExecutions is a function that returns the executions of a workflow
* it returns an array of workflow_execution.WorkflowExecution
*/
func (ws *WorkflowSchedule) GetExecutions(workflow *workflow.Workflow) ([]*WorkflowExecution, error) {
workflows_executions := []*WorkflowExecution{}
dates, err := ws.GetDates()
if err != nil {
return workflows_executions, err
}
for _, date := range dates {
obj := &WorkflowExecution{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(), // set the uuid of the execution
Name: workflow.Name + "_execution_" + date.Start.String(), // set the name of the execution
},
ExecutionsID: ws.UUID,
ExecDate: date.Start, // set the execution date
EndDate: date.End, // set the end date
State: enum.DRAFT, // set the state to 1 (scheduled)
WorkflowID: workflow.GetID(), // set the workflow id dependancy of the execution
}
workflows_executions = append(workflows_executions, obj)
}
return workflows_executions, nil
}
func (ws *WorkflowSchedule) GetDates() ([]Schedule, error) {
schedule := []Schedule{}
if len(ws.Cron) > 0 { // if cron is set then end date should be set
if ws.End == nil {
return schedule, errors.New("a cron task should have an end date")
}
if ws.DurationS <= 0 {
ws.DurationS = ws.End.Sub(ws.Start).Seconds()
}
cronStr := strings.Split(ws.Cron, " ") // split the cron string to treat it
if len(cronStr) < 6 { // if the cron string is less than 6 fields, return an error because format is : ss mm hh dd MM dw (6 fields)
return schedule, errors.New("Bad cron message: (" + ws.Cron + "). Should be at least ss mm hh dd MM dw")
}
subCron := strings.Join(cronStr[:6], " ")
// cron should be parsed as ss mm hh dd MM dw t (min 6 fields)
specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) // create a new cron parser
sched, err := specParser.Parse(subCron) // parse the cron string
if err != nil {
return schedule, errors.New("Bad cron message: " + err.Error())
}
// loop through the cron schedule to set the executions
for s := sched.Next(ws.Start); !s.IsZero() && s.Before(*ws.End); s = sched.Next(s) {
e := s.Add(time.Duration(ws.DurationS) * time.Second)
schedule = append(schedule, Schedule{
Start: s,
End: &e,
})
}
} else { // if no cron, set the execution to the start date
schedule = append(schedule, Schedule{
Start: ws.Start,
End: ws.End,
})
}
return schedule, nil
}
type Schedule struct {
Start time.Time
End *time.Time
}
/*
* TODO : LARGEST GRAIN PLANIFYING THE WORKFLOW WHEN OPTION IS SET
* SET PROTECTION BORDER TIME
*/

View File

@@ -20,13 +20,13 @@ func (d *Workspace) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) // Create a new instance of the accessor return NewAccessor(request) // Create a new instance of the accessor
} }
func (ao *Workspace) VerifyAuth(request *tools.APIRequest) bool { func (ao *Workspace) VerifyAuth(callName string, request *tools.APIRequest) bool {
if ao.Shared != "" { if ao.Shared != "" {
shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(ao.Shared) shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(ao.Shared)
if code != 200 || shared == nil { if code != 200 || shared == nil {
return false return false
} }
return shared.VerifyAuth(request) return shared.VerifyAuth(callName, request)
} }
return ao.AbstractObject.VerifyAuth(request) return ao.AbstractObject.VerifyAuth(callName, request)
} }

View File

@@ -130,7 +130,9 @@ func (a *workspaceMongoAccessor) share(realData *Workspace, method tools.METHOD,
paccess := &peer.Peer{} paccess := &peer.Peer{}
for _, p := range res.(*shallow_collaborative_area.ShallowCollaborativeArea).Peers { for _, p := range res.(*shallow_collaborative_area.ShallowCollaborativeArea).Peers {
paccess.UUID = p paccess.UUID = p
if ok, _ := paccess.IsMySelf(); ok { // If the peer is the current peer, never share because it will create a loop if ok, _ := utils.IsMySelf(p, paccess.GetAccessor(&tools.APIRequest{
Admin: true,
})); ok { // If the peer is the current peer, never share because it will create a loop
continue continue
} }
if method == tools.DELETE { // If the workspace is deleted, share the deletion if method == tools.DELETE { // If the workspace is deleted, share the deletion

View File

@@ -16,6 +16,7 @@ type APIRequest struct {
PeerID string PeerID string
Groups []string Groups []string
Caller *HTTPCaller Caller *HTTPCaller
Admin bool
} }
/* /*
@@ -60,7 +61,9 @@ func (s State) String() string {
type API struct{} type API struct{}
func (a *API) Discovered(infos []*beego.ControllerInfo) { func (a *API) Discovered(infos []*beego.ControllerInfo) {
respondToDiscovery := func(m map[string]interface{}) { respondToDiscovery := func(resp NATSResponse) {
var m map[string]interface{}
json.Unmarshal(resp.Payload, &m)
if len(m) == 0 { if len(m) == 0 {
a.SubscribeRouter(infos) a.SubscribeRouter(infos)
} }
@@ -90,8 +93,10 @@ func (a *API) GetState() (State, int, error) {
return ALIVE, 200, nil // If everything is up, return alive return ALIVE, 200, nil // If everything is up, return alive
} }
func (a *API) ListenRouter(exec func(msg map[string]interface{})) { func (a *API) ListenRouter(exec func(msg NATSResponse)) {
go NewNATSCaller().ListenNats(DISCOVERY.GenerateKey("api"), exec) go NewNATSCaller().ListenNats(map[NATSMethod]func(msg NATSResponse){
DISCOVERY: exec,
})
} }
func (a *API) SubscribeRouter(infos []*beego.ControllerInfo) { func (a *API) SubscribeRouter(infos []*beego.ControllerInfo) {
@@ -110,7 +115,14 @@ func (a *API) SubscribeRouter(infos []*beego.ControllerInfo) {
} }
} }
} }
go nats.SetNATSPub("api", DISCOVERY, discovery) b, _ := json.Marshal(discovery)
go nats.SetNATSPub(DISCOVERY, NATSResponse{
FromApp: beego.AppPath,
Datatype: -1,
Method: int(DISCOVERY),
Payload: b,
})
} }
// CheckRemotePeer checks the state of a remote peer // CheckRemotePeer checks the state of a remote peer

View File

@@ -1,5 +1,7 @@
package tools package tools
import "strings"
type DataType int type DataType int
// DataType - Enum for the different types of resources in db accessible from the outside // DataType - Enum for the different types of resources in db accessible from the outside
@@ -30,6 +32,8 @@ const (
LIVE_STORAGE LIVE_STORAGE
BILL BILL
MINIO_SVCACC MINIO_SVCACC
MINIO_SVCACC_SECRET
NATIVE_TOOL
) )
var NOAPI = "" var NOAPI = ""
@@ -75,6 +79,8 @@ var DefaultAPI = [...]string{
DATACENTERAPI, DATACENTERAPI,
NOAPI, NOAPI,
MINIO, MINIO,
MINIO,
CATALOGAPI,
} }
// Bind the standard data name to the data type // Bind the standard data name to the data type
@@ -105,6 +111,8 @@ var Str = [...]string{
"live_storage", "live_storage",
"bill", "bill",
"service_account", "service_account",
"secret",
"native_tool",
} }
func FromInt(i int) string { func FromInt(i int) string {
@@ -116,6 +124,9 @@ func (d DataType) API() string { // API - Returns the API name of the data type
} }
func (d DataType) String() string { // String - Returns the string name of the data type func (d DataType) String() string { // String - Returns the string name of the data type
if d < 0 {
return ""
}
return Str[d] return Str[d]
} }
@@ -128,5 +139,45 @@ func DataTypeList() []DataType {
return []DataType{DATA_RESOURCE, PROCESSING_RESOURCE, STORAGE_RESOURCE, COMPUTE_RESOURCE, WORKFLOW_RESOURCE, return []DataType{DATA_RESOURCE, PROCESSING_RESOURCE, STORAGE_RESOURCE, COMPUTE_RESOURCE, WORKFLOW_RESOURCE,
WORKFLOW, WORKFLOW_EXECUTION, WORKSPACE, PEER, COLLABORATIVE_AREA, RULE, BOOKING, WORKFLOW_HISTORY, WORKSPACE_HISTORY, WORKFLOW, WORKFLOW_EXECUTION, WORKSPACE, PEER, COLLABORATIVE_AREA, RULE, BOOKING, WORKFLOW_HISTORY, WORKSPACE_HISTORY,
ORDER, PURCHASE_RESOURCE, ADMIRALTY_SOURCE, ADMIRALTY_TARGET, ADMIRALTY_SECRET, ADMIRALTY_KUBECONFIG, ADMIRALTY_NODES, ORDER, PURCHASE_RESOURCE, ADMIRALTY_SOURCE, ADMIRALTY_TARGET, ADMIRALTY_SECRET, ADMIRALTY_KUBECONFIG, ADMIRALTY_NODES,
LIVE_DATACENTER, LIVE_STORAGE, BILL} LIVE_DATACENTER, LIVE_STORAGE, BILL, NATIVE_TOOL}
}
type PropalgationMessage struct {
DataType int `json:"datatype"`
Action PubSubAction `json:"action"`
Payload []byte `json:"payload"`
}
type PubSubAction int
const (
PB_SEARCH PubSubAction = iota
PB_SEARCH_RESPONSE
PB_CREATE
PB_UPDATE
PB_DELETE
NONE
)
func GetActionString(ss string) PubSubAction {
switch ss {
case "search":
return PB_SEARCH
case "create":
return PB_CREATE
case "update":
return PB_UPDATE
case "delete":
return PB_DELETE
case "search_response":
return PB_SEARCH_RESPONSE
default:
return NONE
}
}
var path = []string{"search", "search_response", "create", "update", "delete"}
func (m PubSubAction) String() string {
return strings.ToUpper(path[m])
} }

View File

@@ -3,25 +3,53 @@ package tools
import ( import (
"encoding/json" "encoding/json"
"strings" "strings"
"sync"
"time" "time"
"cloud.o-forge.io/core/oc-lib/config" "cloud.o-forge.io/core/oc-lib/config"
"cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/logs"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
"github.com/rs/zerolog"
) )
type NATSResponse struct {
FromApp string `json:"from_app"`
Datatype DataType `json:"datatype"`
User string `json:"user"`
Method int `json:"method"`
Payload []byte `json:"payload"`
}
// NATS Method Enum defines the different methods that can be used to interact with the NATS server // NATS Method Enum defines the different methods that can be used to interact with the NATS server
type NATSMethod int type NATSMethod int
var meths = []string{"remove execution", "create execution", "discovery",
"workflow event", "remove peer", "create peer", "create resource", "remove resource", "propalgation event", "catalogsearch event"}
const ( const (
REMOVE NATSMethod = iota REMOVE_EXECUTION NATSMethod = iota
CREATE CREATE_EXECTUTION
DISCOVERY DISCOVERY
WORKFLOW_EVENT
REMOVE_PEER
CREATE_PEER
CREATE_RESOURCE
REMOVE_RESOURCE
PROPALGATION_EVENT
CATALOG_SEARCH_EVENT
) )
func (n NATSMethod) String() string {
return meths[n]
}
// NameToMethod returns the NATSMethod enum value from a string // NameToMethod returns the NATSMethod enum value from a string
func NameToMethod(name string) NATSMethod { func NameToMethod(name string) NATSMethod {
for _, v := range [...]NATSMethod{REMOVE, CREATE} { for _, v := range [...]NATSMethod{REMOVE_EXECUTION, CREATE_EXECTUTION, DISCOVERY, WORKFLOW_EVENT,
REMOVE_PEER, CREATE_PEER, CREATE_RESOURCE, REMOVE_RESOURCE, PROPALGATION_EVENT, CATALOG_SEARCH_EVENT} {
if strings.Contains(strings.ToLower(v.String()), strings.ToLower(name)) { if strings.Contains(strings.ToLower(v.String()), strings.ToLower(name)) {
return v return v
} }
@@ -30,13 +58,8 @@ func NameToMethod(name string) NATSMethod {
} }
// GenerateKey generates a key for the NATSMethod usefull for standard key based on data name & method // GenerateKey generates a key for the NATSMethod usefull for standard key based on data name & method
func (d NATSMethod) GenerateKey(name string) string { func (d NATSMethod) GenerateKey() string {
return name + "_" + d.String() return strings.ReplaceAll(d.String(), " ", "_")
}
// String returns the string of the enum
func (d NATSMethod) String() string {
return [...]string{"remove", "create", "discovery"}[d]
} }
type natsCaller struct{} type natsCaller struct{}
@@ -48,7 +71,7 @@ func NewNATSCaller() *natsCaller {
// on workflows' scheduling. Messages must contain // on workflows' scheduling. Messages must contain
// workflow execution ID, to allow retrieval of execution infos // workflow execution ID, to allow retrieval of execution infos
func (s *natsCaller) ListenNats(chanName string, exec func(msg map[string]interface{})) { func (s *natsCaller) ListenNats(execs map[NATSMethod]func(NATSResponse)) {
log := logs.GetLogger() log := logs.GetLogger()
if config.GetConfig().NATSUrl == "" { if config.GetConfig().NATSUrl == "" {
log.Error().Msg(" -> NATS_SERVER is not set") log.Error().Msg(" -> NATS_SERVER is not set")
@@ -57,27 +80,23 @@ func (s *natsCaller) ListenNats(chanName string, exec func(msg map[string]interf
for { for {
nc, err := nats.Connect(config.GetConfig().NATSUrl) nc, err := nats.Connect(config.GetConfig().NATSUrl)
if err != nil { if err != nil {
log.Error().Msg("Could not connect to NATS")
time.Sleep(1 * time.Minute) time.Sleep(1 * time.Minute)
continue continue
} }
ch := make(chan *nats.Msg, 64) defer nc.Close()
subs, err := nc.ChanSubscribe(chanName, ch) var wg sync.WaitGroup
if err != nil { wg.Add(len(execs))
log.Error().Msg("Error listening to NATS : " + err.Error()) for k, v := range execs {
} go s.listenForChange(log, nc, k, v, &wg)
defer subs.Unsubscribe()
for msg := range ch {
map_mess := map[string]interface{}{}
json.Unmarshal(msg.Data, &map_mess)
exec(map_mess)
} }
wg.Wait()
break break
} }
} }
// SetNATSPub sets a message to the NATS server // SetNATSPub sets a message to the NATS server
func (o *natsCaller) SetNATSPub(dataName string, method NATSMethod, data interface{}) string { func (o *natsCaller) SetNATSPub(method NATSMethod, data NATSResponse) string {
if config.GetConfig().NATSUrl == "" { if config.GetConfig().NATSUrl == "" {
return " -> NATS_SERVER is not set" return " -> NATS_SERVER is not set"
} }
@@ -92,7 +111,7 @@ func (o *natsCaller) SetNATSPub(dataName string, method NATSMethod, data interfa
if err != nil { if err != nil {
return " -> " + err.Error() return " -> " + err.Error()
} }
err = nc.Publish(method.GenerateKey(dataName), js) // Publish the message on the NATS server with a channel name based on the data name (or whatever start) and the method err = nc.Publish(method.GenerateKey(), js) // Publish the message on the NATS server with a channel name based on the data name (or whatever start) and the method
if err != nil { if err != nil {
time.Sleep(1 * time.Minute) time.Sleep(1 * time.Minute)
continue continue
@@ -101,3 +120,25 @@ func (o *natsCaller) SetNATSPub(dataName string, method NATSMethod, data interfa
} }
return "" return ""
} }
// Goroutine listening to a NATS server for updates
// on workflows' scheduling. Messages must contain
// workflow execution ID, to allow retrieval of execution infos
func (o *natsCaller) listenForChange(logger zerolog.Logger, nc *nats.Conn, natsTools NATSMethod,
function func(NATSResponse), wg *sync.WaitGroup) {
defer wg.Done()
ch := make(chan *nats.Msg, 64)
logger.Info().Msg("Listening to " + natsTools.GenerateKey())
subs, err := nc.ChanSubscribe(natsTools.GenerateKey(), ch)
if err != nil {
logger.Error().Msg("Error listening to NATS : " + err.Error())
}
defer subs.Unsubscribe()
for msg := range ch {
var resp NATSResponse
json.Unmarshal(msg.Data, &resp)
logger.Info().Msg("Catching " + natsTools.String() + "... " + resp.FromApp + " - " + resp.Datatype.String())
function(resp)
}
}