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,14 +9,17 @@ import "sync"
// ===================================================
type Config struct {
NATSUrl string
MongoUrl string
MongoDatabase string
Host string
Port string
LokiUrl string
LogLevel string
Whitelist bool
APIPort int
NATSUrl string
MongoUrl string
MongoDatabase string
Host string
Port string
LokiUrl string
LogLevel string
Whitelist bool
PrivateKeyPath string
PublicKeyPath string
}
func (c Config) GetUrl() string {
@@ -37,12 +40,13 @@ func GetConfig() *Config {
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().MongoDatabase = database
GetConfig().NATSUrl = natsUrl
GetConfig().LokiUrl = lokiUrl
GetConfig().LogLevel = logLevel
GetConfig().Whitelist = true
GetConfig().APIPort = port
return GetConfig()
}

View File

@@ -2,6 +2,7 @@ package dbs
import (
"fmt"
"runtime/debug"
"strings"
"go.mongodb.org/mongo-driver/bson"
@@ -19,6 +20,8 @@ const (
GT
EQUAL
NOT
ELEMMATCH
OR
)
var str = [...]string{
@@ -31,113 +34,43 @@ var str = [...]string{
"gt",
"equal",
"not",
"elemMatch",
}
func (m Operator) String() string {
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 {
defer func() {
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 {
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:
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:
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:
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:
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:
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:
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:
return bson.M{k: 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.M{"$not": f}
return bson.M{"$not": m.ToValueOperator(StringToOperator(m.String()), value)}
case OR:
return bson.M{"$or": m.ToValueOperator(StringToOperator(m.String()), value)}
default:
return defaultValue
}
@@ -152,13 +85,46 @@ func StringToOperator(s string) Operator {
return LIKE
}
func ToValueOperator(operator Operator, value interface{}) interface{} {
if strings.TrimSpace(fmt.Sprintf("%v", value)) == "*" {
value = ""
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}}
}
}
if operator == LIKE {
return "(?i).*" + strings.TrimSpace(fmt.Sprintf("%v", value)) + ".*"
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)) == "*" {
value = ""
}
if operator == LIKE {
return "(?i).*" + strings.TrimSpace(fmt.Sprintf("%v", 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.SetLimit(1000)
targetDBCollection := CollectionMap[collection_name]
orList := bson.A{}
andList := bson.A{}
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}}
}
}
f := dbs.GetBson(filters)
MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
// defer cancel()

View File

@@ -194,7 +194,7 @@ AccessPricingProfile ^-- ProcessingResourcePricingProfile
ExploitPricingProfile ^-- ComputeResourcePricingProfile
ExploitPricingProfile ^-- StorageResourcePricingProfile
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
}
class AccessPricingProfile {
@@ -319,7 +319,7 @@ Workflow "1 " --* "many " ExploitResourceSet
class Workflow {}
interface PricedItemITF {
getPrice(request) float64, error
GetPriceHT(request) float64, error
}
@enduml

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"os"
"strings"
"runtime/debug"
@@ -30,6 +31,8 @@ import (
"cloud.o-forge.io/core/oc-lib/tools"
beego "github.com/beego/beego/v2/server/web"
"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/rs/zerolog"
)
@@ -57,8 +60,36 @@ const (
LIVE_DATACENTER = tools.LIVE_DATACENTER
LIVE_STORAGE = tools.LIVE_STORAGE
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
func (d LibDataEnum) API() string {
return tools.DefaultAPI[d]
@@ -103,12 +134,13 @@ type LibData struct {
}
func InitDaemon(appName string) {
beego.BConfig.AppName = appName
config.SetAppName(appName) // set the app name to the logger to define the main log chan
// create a temporary console logger for init
logs.SetLogger(logs.CreateLogger("main"))
// Load the right config file
o := GetConfLoader()
// resources.InitNative()
// feed the library with the loaded config
SetConfig(
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("LOKI_URL", ""),
o.GetStringDefault("LOG_LEVEL", "info"),
o.GetIntDefault("API_PORT", 8080),
)
// Beego init
beego.BConfig.AppName = appName
@@ -167,10 +200,22 @@ func ExtractTokenInfo(request http.Request) (string, string, []string) {
return "", "", []string{}
}
func Init(appName string) {
func InitAPI(appName string) {
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.Discovered(beego.BeeApp.Handlers.GetAllControllerInfo())
beego.Run()
}
//
@@ -192,8 +237,8 @@ func GetLogger() zerolog.Logger {
* @param logLevel string
* @return *Config
*/
func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string) *config.Config {
cfg := config.SetConfig(mongoUrl, database, natsUrl, lokiUrl, logLevel)
func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string, port int) *config.Config {
cfg := config.SetConfig(mongoUrl, database, natsUrl, lokiUrl, logLevel, port)
defer func() {
if r := recover(); r != nil {
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 {
collection LibDataEnum
user string
peerID string
groups []string
caller *tools.HTTPCaller
Collection LibDataEnum
User string
PeerID string
Groups []string
Caller *tools.HTTPCaller
admin bool
}
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) {
defer func() {
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 NewRequestAdmin(collection LibDataEnum, caller *tools.HTTPCaller) *Request {
return &Request{Collection: collection, Caller: caller, admin: true}
}
/*
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,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
})*/
})
return nil
}
*/
/*
* Search will search for the data in the database
* @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())}
}
}()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
d, code, err := models.Model(r.Collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.Caller,
Username: r.User,
PeerID: r.PeerID,
Groups: r.Groups,
Admin: r.admin,
}).Search(filters, word, isDraft)
if err != nil {
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())}
}
}()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
d, code, err := models.Model(r.Collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.Caller,
Username: r.User,
PeerID: r.PeerID,
Groups: r.Groups,
Admin: r.admin,
}).LoadAll(isDraft)
if err != nil {
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())}
}
}()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
d, code, err := models.Model(r.Collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.Caller,
Username: r.User,
PeerID: r.PeerID,
Groups: r.Groups,
Admin: r.admin,
}).LoadOne(id)
if err != nil {
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())}
}
}()
model := models.Model(r.collection.EnumIndex())
model := models.Model(r.Collection.EnumIndex())
d, code, err := model.GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
Caller: r.Caller,
Username: r.User,
PeerID: r.PeerID,
Groups: r.Groups,
Admin: r.admin,
}).UpdateOne(model.Deserialize(set, model), id)
if err != nil {
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())}
}
}()
d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
d, code, err := models.Model(r.Collection.EnumIndex()).GetAccessor(&tools.APIRequest{
Caller: r.Caller,
Username: r.User,
PeerID: r.PeerID,
Groups: r.Groups,
Admin: r.admin,
}).DeleteOne(id)
if err != nil {
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())}
}
}()
model := models.Model(r.collection.EnumIndex())
model := models.Model(r.Collection.EnumIndex())
d, code, err := model.GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
Caller: r.Caller,
Username: r.User,
PeerID: r.PeerID,
Groups: r.Groups,
Admin: r.admin,
}).StoreOne(model.Deserialize(object, model))
if err != nil {
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())}
}
}()
model := models.Model(r.collection.EnumIndex())
model := models.Model(r.Collection.EnumIndex())
d, code, err := model.GetAccessor(&tools.APIRequest{
Caller: r.caller,
Username: r.user,
PeerID: r.peerID,
Groups: r.groups,
Caller: r.Caller,
Username: r.User,
PeerID: r.PeerID,
Groups: r.Groups,
Admin: r.admin,
}).CopyOne(model.Deserialize(object, model))
if err != nil {
data = LibData{Data: d, Code: code, Err: err.Error()}
@@ -597,3 +619,64 @@ func (l *LibData) ToPurchasedResource() *purchase_resource.PurchaseResource {
}
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/goraz/onion v0.1.3
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/stretchr/testify v1.10.0
)
@@ -46,7 +45,6 @@ require (
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.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/xdg-go/pbkdf2 v1.0.0 // 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/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
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/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
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/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=

View File

@@ -1,14 +1,16 @@
package bill
import (
"encoding/json"
"fmt"
"sync"
"time"
"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/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"
"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/tools"
@@ -135,20 +137,25 @@ type PeerOrder struct {
}
func (d *PeerOrder) Pay(request *tools.APIRequest, response chan *PeerOrder, wg *sync.WaitGroup) {
d.Status = enum.PENDING
go func() {
// DO SOMETHING TO PAY ON BLOCKCHAIN OR WHATEVER ON RETURN UPDATE STATUS
d.Status = enum.PAID // TO REMOVE LATER IT'S A MOCK
if d.Status == enum.PAID {
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
}
accessor := purchase_resource.NewAccessor(request)
accessor.StoreOne(&purchase_resource.PurchaseResource{
ResourceID: b.Item.GetID(),
ResourceType: b.Item.GetType(),
EndDate: b.Item.GetLocationEnd(),
ResourceID: priced.GetID(),
ResourceType: priced.GetType(),
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 {
for _, b := range d.Items {
tot, err := b.GetPrice(request) // missing something
tot, err := b.GetPriceHT(request) // missing something
if err != nil {
return err
}
@@ -174,14 +181,26 @@ func (d *PeerOrder) SumUpBill(request *tools.APIRequest) error {
type PeerItemOrder struct {
Quantity int `json:"quantity,omitempty" bson:"quantity,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)
search, code, _ := accessor.Search(&dbs.Filters{
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)
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 {
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/models/common/enum"
"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/tools"
"go.mongodb.org/mongo-driver/bson/primitive"
@@ -16,14 +15,14 @@ import (
* Booking is a struct that represents a booking
*/
type Booking struct {
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
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
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"`
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
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
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
@@ -119,7 +118,7 @@ func (d *Booking) GetAccessor(request *tools.APIRequest) utils.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
}

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) {
assert.True(t, (&booking.Booking{}).VerifyAuth(nil))
assert.True(t, (&booking.Booking{}).VerifyAuth("get", nil))
}
func TestBooking_StoreDraftDefault(t *testing.T) {

View File

@@ -71,7 +71,7 @@ func (ao *CollaborativeArea) Clear(peerID string) {
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 grps, ok := ao.AllowedPeersGroup[request.PeerID]; 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 {
@@ -97,7 +97,7 @@ func (d *CollaborativeArea) Trim() *CollaborativeArea {
func (d *CollaborativeArea) StoreDraftDefault() {
d.AllowedPeersGroup = map[string][]string{
d.CreatorID: []string{"*"},
d.CreatorID: {"*"},
}
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
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
// retrieve or proper peer
if data.(*CollaborativeArea).CollaborativeAreaRule != nil {
@@ -271,7 +276,9 @@ func (a *collaborativeAreaMongoAccessor) contactPeer(shared *CollaborativeArea,
paccess := (&peer.Peer{})
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
}
shared.IsSent = meth == tools.POST

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ type PricingProfileITF interface {
IsPurchasable() bool
GetPurchase() BuyingStrategy
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
@@ -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
}
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 {
return -1
}
func GetDefaultPricingProfile() PricingProfileITF {
return &AccessPricingProfile[TimePricingStrategy]{
Pricing: PricingStrategy[TimePricingStrategy]{
Price: 0,
Currency: "EUR",
BuyingStrategy: PERMANENT,
TimePricingStrategy: ONCE,
},
}
}
type ExploitPrivilegeStrategy int
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]
}
func TimePricingStrategyListStr() []string {
return []string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}
}
func TimePricingStrategyList() []TimePricingStrategy {
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
}
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 {
return BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end)
} else if p.BuyingStrategy == PERMANENT {
price, err := BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end)
if err != nil {
return 0, err
}
if variations != nil {
for _, v := range variations {
price = v.GetPriceHT(price)
}
return 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
}
@@ -179,3 +209,18 @@ func (p PricingStrategy[T]) GetTimePricingStrategy() TimePricingStrategy {
func (p PricingStrategy[T]) GetOverrideStrategy() T {
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())
}
func TestPricingStrategy_GetPrice(t *testing.T) {
func TestPricingStrategy_GetPriceHT(t *testing.T) {
start := time.Now()
end := start.Add(1 * time.Hour)
@@ -111,19 +111,19 @@ func TestPricingStrategy_GetPrice(t *testing.T) {
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.True(t, p > 0)
// UNLIMITED case
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.Equal(t, 5.0, p)
// PAY_PER_USE case
//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.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.STORAGE_RESOURCE.String(): func() utils.DBObject { return &resource.StorageResource{} },
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_EXECUTION.String(): func() utils.DBObject { return &workflow_execution.WorkflowExecution{} },
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)
}
func (d *Order) SetName() {
func (d *Order) SetName(_ string) {
d.Name = d.UUID + "_order_" + "_" + time.Now().UTC().Format("2006-01-02T15:04:05")
}

View File

@@ -2,20 +2,18 @@ package peer
import (
"fmt"
"strings"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
)
// now write a go enum for the state partner with self, blacklist, partner
type PeerState int
const (
NONE PeerState = iota
SELF
PARTNER
BLACKLIST
OFFLINE PeerState = iota
ONLINE
)
func (m PeerState) String() string {
@@ -26,18 +24,58 @@ func (m PeerState) EnumIndex() int {
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
type Peer struct {
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
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"`
Relation PeerRelation `json:"relation" bson:"state" default:"0"`
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
}
func (ao *Peer) VerifyAuth(request *tools.APIRequest) bool {
func (ao *Peer) VerifyAuth(callName string, request *tools.APIRequest) bool {
return true
}
@@ -66,16 +104,6 @@ func (ao *Peer) RemoveExecution(exec PeerExecution) {
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
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

View File

@@ -28,19 +28,19 @@ type PeerCache struct {
}
// 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-", "")
}
// 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{}
access := NewShallowAccessor()
res, code, _ := access.LoadOne(peerID) // Load the peer from db
if code != 200 { // no peer no party
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)
res.(*Peer).ServicesState = services // Update the services states of the peer
access.UpdateOne(res, peerID) // Update the peer in the db
@@ -61,11 +61,11 @@ func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
url := ""
// 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
pexec := &PeerExecution{
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,
DataType: dt.EnumIndex(),
DataID: dataID,
@@ -78,7 +78,7 @@ func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
return map[string]interface{}{}, errors.New("peer " + peerID + " not found")
}
// 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
mypeer.FailedExecution = []PeerExecution{} // Reset the failed executions list
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 {
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{
Or: map[string][]dbs.Filter{ // search by name if no filters are provided
"state": {{Operator: dbs.EQUAL.String(), Value: i}},
},
Or: m,
}
} else {
if search == "*" {
search = ""
}
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
"abstractobject.name": {{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 {
return &peer.Peer{
Url: "http://localhost",
NATSAddress: "",
StreamAddress: "127.0.0.1",
APIUrl: "http://localhost",
WalletAddress: "0x123",
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/utils"
"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()
}
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 {
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)
return &PricedComputeResource{
PricedResource: *priced,
}
}, nil
}
type ComputeNode struct {
@@ -60,6 +64,17 @@ type ComputeResourceInstance struct {
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 {
ResourcePartnerShip[*ComputeResourcePricingProfile]
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
// 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 {
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 {
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 {
return 0, err
}
@@ -121,7 +136,7 @@ func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitD
if _, ok := p.GPUsPrices[model]; ok {
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 {
return 0, err
}
@@ -131,7 +146,7 @@ func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitD
if p.RAMPrice >= 0 {
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 {
return 0, err
}
@@ -152,14 +167,17 @@ func (r *PricedComputeResource) GetType() tools.DataType {
return tools.COMPUTE_RESOURCE
}
func (r *PricedComputeResource) GetPrice() (float64, error) {
now := time.Now()
if r.UsageStart == nil {
r.UsageStart = &now
func (r *PricedComputeResource) GetPriceHT() (float64, error) {
if r.BookingConfiguration == nil {
r.BookingConfiguration = &BookingConfiguration{}
}
if r.UsageEnd == nil {
add := r.UsageStart.Add(time.Duration(1 * time.Hour))
r.UsageEnd = &add
now := time.Now()
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 {
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)
for _, l := range []map[string]float64{r.CPUsLocated, r.GPUsLocated} {
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 {
return 0, err
}
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 {
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/utils"
"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()
}
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 {
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)
return &PricedDataResource{
PricedResource: *priced,
}
}, nil
}
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
}
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() {
found := false
for _, p := range ri.ResourceInstance.Env {
@@ -89,7 +104,9 @@ const (
)
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 {
@@ -101,7 +118,9 @@ func ToDataResourcePricingStrategy(i int) DataResourcePricingStrategy {
}
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 {
@@ -132,14 +151,6 @@ func (p *DataResourcePricingProfile) GetOverrideStrategyValue() int {
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 {
return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
}
@@ -158,15 +169,18 @@ func (r *PricedDataResource) GetType() tools.DataType {
return tools.DATA_RESOURCE
}
func (r *PricedDataResource) GetPrice() (float64, error) {
fmt.Println("GetPrice", r.UsageStart, r.UsageEnd)
now := time.Now()
if r.UsageStart == nil {
r.UsageStart = &now
func (r *PricedDataResource) GetPriceHT() (float64, error) {
if r.BookingConfiguration == nil {
r.BookingConfiguration = &BookingConfiguration{}
}
if r.UsageEnd == nil {
add := r.UsageStart.Add(time.Duration(1 * time.Hour))
r.UsageEnd = &add
fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd)
now := time.Now()
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 {
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 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
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/utils"
"cloud.o-forge.io/core/oc-lib/tools"
@@ -9,11 +11,15 @@ import (
type ResourceInterface interface {
utils.DBObject
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
GetSelectedInstance() ResourceInstanceITF
GetSelectedInstance(selected *int) ResourceInstanceITF
ClearEnv() utils.DBObject
SetAllowedInstances(request *tools.APIRequest)
AddInstances(instance ResourceInstanceITF)
RefineResourceByPartnership(peerID string) ResourceInterface
}
type ResourceInstanceITF interface {
@@ -22,17 +28,17 @@ type ResourceInstanceITF interface {
GetName() string
StoreDraftDefault()
ClearEnv()
GetProfile() pricing.PricingProfileITF
GetProfile(peerID string, partnershipIndex *int, buying *int, strategy *int) pricing.PricingProfileITF
GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string)
ClearPeerGroups()
GetSelectedPartnership(peerID string, groups []string) ResourcePartnerITF
GetPartnerships(peerID string, groups []string) []ResourcePartnerITF
RefineResourceByPartnership(peerID string) (ResourceInstanceITF, bool)
}
type ResourcePartnerITF interface {
RefineResourceByPartnership(peerID string) (ResourcePartnerITF, bool)
GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
GetPeerGroups() map[string][]string
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"`
Computes []string `bson:"computes,omitempty" json:"computes,omitempty"`
Workflows []string `bson:"workflows,omitempty" json:"workflows,omitempty"`
NativeTool []string `bson:"native,omitempty" json:"native,omitempty"`
DataResources []*DataResource `bson:"-" json:"data_resources,omitempty"`
StorageResources []*StorageResource `bson:"-" json:"storage_resources,omitempty"`
ProcessingResources []*ProcessingResource `bson:"-" json:"processing_resources,omitempty"`
ComputeResources []*ComputeResource `bson:"-" json:"compute_resources,omitempty"`
WorkflowResources []*WorkflowResource `bson:"-" json:"workflow_resources,omitempty"`
NativeTools []*NativeTool `bson:"-" json:"native_tools,omitempty"`
}
func (r *ResourceSet) Clear() {
@@ -62,4 +64,5 @@ type ItemResource struct {
Storage *StorageResource `bson:"storage,omitempty" json:"storage,omitempty"`
Compute *ComputeResource `bson:"compute,omitempty" json:"compute,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,21 +5,37 @@ import (
"fmt"
"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/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 {
Name string `json:"name,omitempty" bson:"name,omitempty"`
Logo string `json:"logo,omitempty" bson:"logo,omitempty"`
InstancesRefs map[string]string `json:"instances_refs,omitempty" bson:"instances_refs,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"`
UsageStart *time.Time `json:"start,omitempty" bson:"start,omitempty"`
UsageEnd *time.Time `json:"end,omitempty" bson:"end,omitempty"`
CreatorID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
ResourceID string `json:"resource_id,omitempty" bson:"resource_id,omitempty"`
ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty"`
Name string `json:"name,omitempty" bson:"name,omitempty"`
Logo string `json:"logo,omitempty" bson:"logo,omitempty"`
InstancesRefs map[string]string `json:"instances_refs,omitempty" bson:"instances_refs,omitempty"`
SelectedPricing pricing.PricingProfileITF `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"`
Quantity int `json:"quantity,omitempty" bson:"quantity,omitempty"`
BookingConfiguration *BookingConfiguration `json:"booking_configuration,omitempty" bson:"booking_configuration,omitempty"`
Variations []*pricing.PricingVariation `json:"pricing_variations" bson:"pricing_variations"`
CreatorID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
ResourceID string `json:"resource_id,omitempty" bson:"resource_id,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 {
@@ -46,6 +62,7 @@ func (abs *PricedResource) IsPurchasable() 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 {
return false
}
@@ -53,48 +70,73 @@ func (abs *PricedResource) IsBooked() bool {
}
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 {
return abs.UsageStart
if abs.BookingConfiguration == nil {
return nil
}
return abs.BookingConfiguration.UsageStart
}
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) {
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 {
if abs.ExplicitBookingDurationS == 0 {
if abs.UsageEnd == nil && abs.UsageStart == nil {
if abs.BookingConfiguration == 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()
}
if abs.UsageEnd == nil {
add := abs.UsageStart.Add(time.Duration(1 * time.Hour))
abs.UsageEnd = &add
if abs.BookingConfiguration.UsageEnd == nil {
add := abs.BookingConfiguration.UsageStart.Add(time.Duration(1 * time.Hour))
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) {
fmt.Println("GetPrice", r.UsageStart, r.UsageEnd)
func (r *PricedResource) GetPriceHT() (float64, error) {
now := time.Now()
if r.UsageStart == nil {
r.UsageStart = &now
if r.BookingConfiguration == nil {
r.BookingConfiguration = &BookingConfiguration{}
}
if r.UsageEnd == nil {
add := r.UsageStart.Add(time.Duration(1 * time.Hour))
r.UsageEnd = &add
fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd)
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 {
return 0, errors.New("pricing profile must be set on Priced Resource " + r.ResourceID)
}
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/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
)
type ProcessingUsage struct {
@@ -26,6 +27,7 @@ type ProcessingUsage struct {
*/
type ProcessingResource struct {
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
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
@@ -47,6 +49,21 @@ type ProcessingInstance struct {
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 {
PricedResource
IsService bool
@@ -57,16 +74,19 @@ func (r *PricedProcessingResource) GetType() tools.DataType {
}
func (a *PricedProcessingResource) GetExplicitDurationInS() float64 {
if a.ExplicitBookingDurationS == 0 {
if a.IsService || a.UsageStart == nil {
if a.BookingConfiguration == nil {
a.BookingConfiguration = &BookingConfiguration{}
}
if a.BookingConfiguration.ExplicitBookingDurationS == 0 {
if a.IsService || a.BookingConfiguration.UsageStart == nil {
if a.IsService {
return -1
}
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 {
@@ -84,11 +104,3 @@ func (p *ProcessingResourcePricingProfile) IsPurchasable() bool {
func (p *ProcessingResourcePricingProfile) IsBooked() bool {
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,19 +3,18 @@ package purchase_resource
import (
"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/tools"
)
type PurchaseResource struct {
utils.AbstractObject
DestPeerID string
PricedItem pricing.PricedItemITF `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
EndDate *time.Time `json:"end_buying_date,omitempty" bson:"end_buying_date,omitempty"`
ResourceID string `json:"resource_id" bson:"resource_id" validate:"required"`
ResourceType tools.DataType `json:"resource_type" bson:"resource_type" validate:"required"`
DestPeerID string `json:"dest_peer_id" bson:"dest_peer_id"`
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
EndDate *time.Time `json:"end_buying_date,omitempty" bson:"end_buying_date,omitempty"`
ResourceID string `json:"resource_id" bson:"resource_id" validate:"required"`
ResourceType tools.DataType `json:"resource_type" bson:"resource_type" validate:"required"`
}
func (d *PurchaseResource) GetAccessor(request *tools.APIRequest) utils.Accessor {

View File

@@ -1,30 +1,52 @@
package resources
import (
"encoding/json"
"errors"
"slices"
"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/pricing"
"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/tools"
"github.com/biter777/countries"
"github.com/google/uuid"
)
// AbstractResource is the struct containing all of the attributes commons to all ressources
type AbstractResource struct {
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
Type string `json:"type,omitempty" bson:"type,omitempty"` // Type is the type of the resource
Logo string `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"` // Logo is the logo of the resource
Description string `json:"description,omitempty" bson:"description,omitempty"` // Description is the 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
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
utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
Type string `json:"type,omitempty" bson:"type,omitempty"` // Type is the type of the resource
Logo string `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"` // Logo is the logo of the resource
Description string `json:"description,omitempty" bson:"description,omitempty"` // Description is the 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
UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"`
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
}
@@ -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
}
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{}
profiles := []pricing.PricingProfileITF{}
for _, instance := range abs.Instances {
for _, instance := range abs.Instances { // TODO why it crush before ?
instances[instance.GetID()] = instance.GetName()
profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups)
}
var profile pricing.PricingProfileITF
if t := abs.GetSelectedInstance(); t != nil {
profile = t.GetProfile()
if t := abs.GetSelectedInstance(selectedInstance); t != nil {
profile = t.GetProfile(request.PeerID, selectedPartnership, selectedBuyingStrategy, selectedStrategy)
}
if profile == nil && len(profiles) > 0 {
profile = profiles[0]
if profile == nil {
if len(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{
Name: abs.Name,
Logo: abs.Logo,
ResourceID: abs.UUID,
ResourceType: t,
Quantity: 1,
InstancesRefs: instances,
SelectedPricing: profile,
Variations: variations,
CreatorID: abs.CreatorID,
}
}, nil
}
func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject {
@@ -84,9 +139,9 @@ func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject {
return abs
}
func (r *AbstractInstanciatedResource[T]) GetSelectedInstance() ResourceInstanceITF {
if r.SelectedInstanceIndex != nil && len(r.Instances) > *r.SelectedInstanceIndex {
return r.Instances[*r.SelectedInstanceIndex]
func (r *AbstractInstanciatedResource[T]) GetSelectedInstance(selected *int) ResourceInstanceITF {
if selected != nil && len(r.Instances) > *selected {
return r.Instances[*selected]
}
if len(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 != "" {
return
}
abs.Instances = VerifyAuthAction[T](abs.Instances, request)
abs.Instances = VerifyAuthAction(abs.Instances, request)
}
func (d *AbstractInstanciatedResource[T]) Trim() {
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 {
instance.ClearPeerGroups()
}
}
}
func (abs *AbstractInstanciatedResource[T]) VerifyAuth(request *tools.APIRequest) bool {
return len(VerifyAuthAction[T](abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(request)
func (abs *AbstractInstanciatedResource[T]) VerifyAuth(callName string, request *tools.APIRequest) bool {
return len(VerifyAuthAction(abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(callName, request)
}
func VerifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.APIRequest) []T {
@@ -145,16 +202,39 @@ type GeoPoint struct {
type ResourceInstance[T ResourcePartnerITF] struct {
utils.AbstractObject
Location GeoPoint `json:"location,omitempty" bson:"location,omitempty"`
Country countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
AccessProtocol string `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"`
Env []models.Param `json:"env,omitempty" bson:"env,omitempty"`
Inputs []models.Param `json:"inputs,omitempty" bson:"inputs,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"`
Location GeoPoint `json:"location,omitempty" bson:"location,omitempty"`
Country countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
AccessProtocol string `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"`
Env []models.Param `json:"env,omitempty" bson:"env,omitempty"`
Inputs []models.Param `json:"inputs,omitempty" bson:"inputs,omitempty"`
Outputs []models.Param `json:"outputs,omitempty" bson:"outputs,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() {
@@ -163,40 +243,31 @@ func (ri *ResourceInstance[T]) ClearEnv() {
ri.Outputs = []models.Param{}
}
func (ri *ResourceInstance[T]) GetProfile() pricing.PricingProfileITF {
if len(ri.Partnerships) > ri.SelectedPartnershipIndex {
prts := ri.Partnerships[ri.SelectedPartnershipIndex]
return prts.GetProfile(ri.SelectedBuyingStrategy, ri.SelectedBuyingStrategy)
func (ri *ResourceInstance[T]) GetProfile(peerID string, partnershipIndex *int, buyingIndex *int, strategyIndex *int) pricing.PricingProfileITF {
if partnershipIndex != nil && len(ri.Partnerships) > *partnershipIndex {
prts := ri.Partnerships[*partnershipIndex]
return prts.GetProfile(buyingIndex, strategyIndex)
}
if ok, _ := utils.IsMySelf(peerID, (&peer.Peer{}).GetAccessor(&tools.APIRequest{
Admin: true,
})); ok {
return pricing.GetDefaultPricingProfile()
}
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 {
pricings := []pricing.PricingProfileITF{}
for _, p := range ri.Partnerships {
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
}
@@ -207,6 +278,20 @@ func (ri *ResourceInstance[T]) GetPeerGroups() ([]ResourcePartnerITF, []map[stri
partners = append(partners, p)
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
}
@@ -223,12 +308,28 @@ type ResourcePartnerShip[T pricing.PricingProfileITF] struct {
// to upgrade pricing profiles. to be a map BuyingStrategy, map of Strategy
}
func (ri *ResourcePartnerShip[T]) GetProfile(buying int, strategy int) pricing.PricingProfileITF {
if strat, ok := ri.PricingProfiles[buying]; ok {
if profile, ok := strat[strategy]; ok {
return profile
func (ri *ResourcePartnerShip[T]) RefineResourceByPartnership(peerID string) (ResourcePartnerITF, bool) {
ok := false
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 nil
}
@@ -254,13 +355,70 @@ func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []st
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
}
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
}
func (rp *ResourcePartnerShip[T]) ClearPeerGroups() {
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{
tools.COMPUTE_RESOURCE, tools.STORAGE_RESOURCE,
tools.PROCESSING_RESOURCE, tools.WORKFLOW_RESOURCE,
tools.DATA_RESOURCE,
tools.DATA_RESOURCE, tools.NATIVE_TOOL,
}, t) {
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/utils"
"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()
}
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 {
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)
return &PricedStorageResource{
PricedResource: *priced,
}
}, nil
}
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
}
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() {
ri.Env = []models.Param{}
ri.Inputs = []models.Param{}
@@ -115,11 +130,15 @@ func StorageResourcePricingStrategyList() []StorageResourcePricingStrategy {
}
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 {
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 {
@@ -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
}
func (p *StorageResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
return p.Pricing.BuyingStrategy
}
func (p *StorageResourcePricingProfile) IsPurchasable() bool {
return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
}
@@ -165,10 +180,6 @@ func (p *StorageResourcePricingProfile) IsBooked() bool {
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 {
PricedResource
UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
@@ -178,15 +189,18 @@ func (r *PricedStorageResource) GetType() tools.DataType {
return tools.STORAGE_RESOURCE
}
func (r *PricedStorageResource) GetPrice() (float64, error) {
fmt.Println("GetPrice", r.UsageStart, r.UsageEnd)
now := time.Now()
if r.UsageStart == nil {
r.UsageStart = &now
func (r *PricedStorageResource) GetPriceHT() (float64, error) {
if r.BookingConfiguration == nil {
r.BookingConfiguration = &BookingConfiguration{}
}
if r.UsageEnd == nil {
add := r.UsageStart.Add(time.Duration(1 * time.Hour))
r.UsageEnd = &add
fmt.Println("GetPriceHT", r.BookingConfiguration.UsageStart, r.BookingConfiguration.UsageEnd)
now := time.Now()
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 {
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 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.UUID = "comp123"
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.IsType(t, &resources.PricedComputeResource{}, result)
}
func TestComputeResourcePricingProfile_GetPrice_CPUs(t *testing.T) {
func TestComputeResourcePricingProfile_GetPriceHT_CPUs(t *testing.T) {
start := time.Now()
end := start.Add(1 * time.Hour)
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)
assert.Greater(t, price, float64(0))
}
func TestComputeResourcePricingProfile_GetPrice_InvalidParams(t *testing.T) {
func TestComputeResourcePricingProfile_GetPriceHT_InvalidParams(t *testing.T) {
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.Equal(t, "params must be set", err.Error())
}
func TestPricedComputeResource_GetPrice(t *testing.T) {
func TestPricedComputeResource_GetPriceHT(t *testing.T) {
start := time.Now()
end := start.Add(1 * time.Hour)
r := resources.PricedComputeResource{
PricedResource: resources.PricedResource{
ResourceID: "comp456",
UsageStart: &start,
UsageEnd: &end,
ExplicitBookingDurationS: 3600,
ResourceID: "comp456",
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &start,
UsageEnd: &end,
ExplicitBookingDurationS: 3600,
},
},
CPUsLocated: map[string]float64{"Xeon": 2},
GPUsLocated: map[string]float64{"Tesla": 1},
RAMLocated: 4,
}
price, err := r.GetPrice()
price, err := r.GetPriceHT()
require.NoError(t, err)
assert.Greater(t, price, float64(0))
}
func TestPricedComputeResource_GetPrice_MissingProfile(t *testing.T) {
func TestPricedComputeResource_GetPriceHT_MissingProfile(t *testing.T) {
r := resources.PricedComputeResource{
PricedResource: resources.PricedResource{
ResourceID: "comp789",
},
}
_, err := r.GetPrice()
_, err := r.GetPriceHT()
require.Error(t, err)
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) {
d := &resources.DataResource{}
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)
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)
}
@@ -80,7 +80,7 @@ func TestDataResourcePricingProfile_IsPurchased(t *testing.T) {
assert.True(t, profile.IsPurchasable())
}
func TestPricedDataResource_GetPrice(t *testing.T) {
func TestPricedDataResource_GetPriceHT(t *testing.T) {
now := time.Now()
later := now.Add(1 * time.Hour)
mockPrice := 42.0
@@ -92,23 +92,25 @@ func TestPricedDataResource_GetPrice(t *testing.T) {
r := &resources.PricedDataResource{
PricedResource: resources.PricedResource{
UsageStart: &now,
UsageEnd: &later,
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &now,
UsageEnd: &later,
},
},
}
price, err := r.GetPrice()
price, err := r.GetPriceHT()
require.NoError(t, err)
assert.Equal(t, mockPrice, price)
}
func TestPricedDataResource_GetPrice_NoProfiles(t *testing.T) {
func TestPricedDataResource_GetPriceHT_NoProfiles(t *testing.T) {
r := &resources.PricedDataResource{
PricedResource: resources.PricedResource{
ResourceID: "test-resource",
},
}
_, err := r.GetPrice()
_, err := r.GetPriceHT()
assert.Error(t, err)
assert.Contains(t, err.Error(), "pricing profile must be set")
}

View File

@@ -26,7 +26,7 @@ func (m *MockPricingProfile) IsPurchasable() bool {
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 {
return 0, errors.New("mock error")
}
@@ -72,14 +72,21 @@ func TestGetAndSetLocationStartEnd(t *testing.T) {
func TestGetExplicitDurationInS(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())
})
t.Run("computes duration from start and end", func(t *testing.T) {
start := time.Now()
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)
})
@@ -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) {
r := &resources.PricedResource{ResourceID: "no-profile"}
price, err := r.GetPrice()
price, err := r.GetPriceHT()
require.Error(t, err)
assert.Contains(t, err.Error(), "pricing profile must be set")
assert.Equal(t, 0.0, price)
@@ -102,24 +109,28 @@ func TestGetPrice(t *testing.T) {
start := time.Now()
end := start.Add(30 * time.Minute)
r := &resources.PricedResource{
UsageStart: &start,
UsageEnd: &end,
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &start,
UsageEnd: &end,
},
}
price, err := r.GetPrice()
price, err := r.GetPriceHT()
require.NoError(t, err)
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()
end := start.Add(1 * time.Hour)
mock := &MockPricingProfile{ReturnErr: true}
r := &resources.PricedResource{
SelectedPricing: mock,
UsageStart: &start,
UsageEnd: &end,
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &start,
UsageEnd: &end,
},
}
price, err := r.GetPrice()
price, err := r.GetPriceHT()
require.Error(t, err)
assert.Equal(t, 0.0, price)
})
@@ -130,10 +141,12 @@ func TestGetPrice(t *testing.T) {
mock := &MockPricingProfile{ReturnCost: 10.0}
r := &resources.PricedResource{
SelectedPricing: mock,
UsageStart: &start,
UsageEnd: &end,
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &start,
UsageEnd: &end,
},
}
price, err := r.GetPrice()
price, err := r.GetPriceHT()
require.NoError(t, err)
assert.Equal(t, 10.0, price)
})

View File

@@ -5,6 +5,7 @@ import (
"time"
"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/tools"
"github.com/stretchr/testify/assert"
@@ -40,7 +41,9 @@ func TestPricedProcessingResource_GetExplicitDurationInS(t *testing.T) {
name: "Nil start time, non-service",
input: PricedProcessingResource{
PricedResource: PricedResource{
UsageStart: nil,
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: nil,
},
},
},
expected: float64((1 * time.Hour).Seconds()),
@@ -49,8 +52,10 @@ func TestPricedProcessingResource_GetExplicitDurationInS(t *testing.T) {
name: "Duration computed from start and end",
input: PricedProcessingResource{
PricedResource: PricedResource{
UsageStart: &now,
UsageEnd: &after,
BookingConfiguration: &resources.BookingConfiguration{
UsageStart: &now,
UsageEnd: &after,
},
},
},
expected: float64((2 * time.Hour).Seconds()),
@@ -59,7 +64,9 @@ func TestPricedProcessingResource_GetExplicitDurationInS(t *testing.T) {
name: "Explicit duration takes precedence",
input: PricedProcessingResource{
PricedResource: PricedResource{
ExplicitBookingDurationS: 1337,
BookingConfiguration: &resources.BookingConfiguration{
ExplicitBookingDurationS: 1337,
},
},
},
expected: 1337,
@@ -80,7 +87,7 @@ func TestProcessingResource_GetAccessor(t *testing.T) {
assert.NotNil(t, acc)
}
func TestProcessingResourcePricingProfile_GetPrice(t *testing.T) {
func TestProcessingResourcePricingProfile_GetPriceHT(t *testing.T) {
start := time.Now()
end := start.Add(2 * time.Hour)
mockPricing := pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
@@ -88,8 +95,8 @@ func TestProcessingResourcePricingProfile_GetPrice(t *testing.T) {
Price: 100.0,
},
}
profile := &ProcessingResourcePricingProfile{mockPricing}
price, err := profile.GetPrice(0, 0, start, end)
profile := &ProcessingResourcePricingProfile{AccessPricingProfile: mockPricing}
price, err := profile.GetPriceHT(0, 0, start, end, []*pricing.PricingVariation{})
assert.NoError(t, err)
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) ClearEnv() {}
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
}
func (m *MockInstance) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
@@ -36,7 +36,11 @@ type MockPartner struct {
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
}
@@ -62,10 +66,10 @@ func TestGetSelectedInstance_WithValidIndex(t *testing.T) {
inst1 := &MockInstance{ID: "1"}
inst2 := &MockInstance{ID: "2"}
resource := &resources.AbstractInstanciatedResource[*MockInstance]{
AbstractResource: resources.AbstractResource{SelectedInstanceIndex: &index},
AbstractResource: resources.AbstractResource{},
Instances: []*MockInstance{inst1, inst2},
}
result := resource.GetSelectedInstance()
result := resource.GetSelectedInstance(&index)
assert.Equal(t, inst2, result)
}
@@ -74,7 +78,7 @@ func TestGetSelectedInstance_NoIndex(t *testing.T) {
resource := &resources.AbstractInstanciatedResource[*MockInstance]{
Instances: []*MockInstance{inst},
}
result := resource.GetSelectedInstance()
result := resource.GetSelectedInstance(nil)
assert.Equal(t, inst, result)
}
@@ -103,9 +107,9 @@ type FakeResource struct {
resources.AbstractInstanciatedResource[*MockInstance]
}
func (f *FakeResource) Trim() {}
func (f *FakeResource) SetAllowedInstances(*tools.APIRequest) {}
func (f *FakeResource) VerifyAuth(*tools.APIRequest) bool { return true }
func (f *FakeResource) Trim() {}
func (f *FakeResource) SetAllowedInstances(*tools.APIRequest) {}
func (f *FakeResource) VerifyAuth(string, *tools.APIRequest) bool { return true }
func TestNewAccessor_ReturnsValid(t *testing.T) {
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.AbstractInstanciatedResource.CreatorID = "creator"
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.IsType(t, &resources.PricedStorageResource{}, priced)
}
func TestStorageResource_ConvertToPricedResource_InvalidType(t *testing.T) {
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)
}
@@ -94,12 +94,12 @@ func TestStorageResourcePricingStrategy_GetQuantity_Invalid(t *testing.T) {
assert.Equal(t, 0.0, q)
}
func TestPricedStorageResource_GetPrice_NoProfiles(t *testing.T) {
func TestPricedStorageResource_GetPriceHT_NoProfiles(t *testing.T) {
res := &resources.PricedStorageResource{
PricedResource: resources.PricedResource{
ResourceID: "res-id",
},
}
_, err := res.GetPrice()
_, err := res.GetPriceHT()
assert.Error(t, err)
}

View File

@@ -32,7 +32,7 @@ func TestWorkflowResource_ConvertToPricedResource(t *testing.T) {
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, 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{} })
}
func (abs *WorkflowResource) RefineResourceByPartnership(peerID string) ResourceInterface {
return abs
}
func (r *WorkflowResource) AddInstances(instance ResourceInstanceITF) {
}
func (r *WorkflowResource) GetType() string {
return tools.WORKFLOW_RESOURCE.String()
}
@@ -34,12 +41,15 @@ func (w *WorkflowResource) SetAllowedInstances(request *tools.APIRequest) {
/* 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{
Name: w.Name,
Logo: w.Logo,
ResourceID: w.UUID,
ResourceType: t,
Quantity: 1,
CreatorID: w.CreatorID,
}
}, nil // TODO ???
}
// TODO : as instanciated resource !

View File

@@ -47,6 +47,10 @@ func (r *AbstractObject) SetID(id string) {
r.UUID = id
}
func (r *AbstractObject) SetName(name string) {
r.Name = name
}
func (r *AbstractObject) GenerateID() {
if r.UUID == "" {
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 {
return ao.AccessMode == Public || (request != nil && ao.CreatorID == request.PeerID && request.PeerID != "")
func (ao *AbstractObject) VerifyAuth(callName string, request *tools.APIRequest) bool {
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 {
if search == "*" {
search = ""

View File

@@ -2,9 +2,11 @@ package utils
import (
"errors"
"os"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/dbs/mongo"
"github.com/google/uuid"
mgb "go.mongodb.org/mongo-driver/mongo"
)
@@ -18,7 +20,7 @@ func VerifyAccess(a Accessor, id string) error {
if err != nil {
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 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())
}
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)
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())
if err != nil {
@@ -68,7 +70,7 @@ func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) {
if err != nil {
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())
}
_, 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
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())
}
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
}
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 f(data)
@@ -132,7 +134,7 @@ func genericLoadAll[T DBObject](res *mgb.Cursor, code int, err error, onlyDraft
return nil, 404, err
}
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
}
objs = append(objs, f(r))
@@ -164,3 +166,42 @@ func GenericRawUpdateOne(set DBObject, id string, a Accessor) (DBObject, int, er
}
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)
GetID() string
GetName() string
SetName(name string)
IsDrafted() bool
CanDelete() bool
StoreDraftDefault()
GetCreatorID() string
UpToDate(user string, peer string, create bool)
CanUpdate(set DBObject) (bool, DBObject)
VerifyAuth(request *tools.APIRequest) bool
VerifyAuth(callName string, request *tools.APIRequest) bool
Serialize(obj DBObject) map[string]interface{}
GetAccessor(request *tools.APIRequest) Accessor
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) {
request := &tools.APIRequest{PeerID: "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}
assert.True(t, ao.VerifyAuth(nil))
assert.True(t, ao.VerifyAuth("get", nil))
ao = &utils.AbstractObject{AccessMode: utils.Private, CreatorID: "peer123"}
request = &tools.APIRequest{PeerID: "wrong"}
assert.False(t, ao.VerifyAuth(request))
assert.False(t, ao.VerifyAuth("get", request))
}
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
}
func NewGraph() *Graph {
return &Graph{
Partial: false,
Zoom: 1,
Items: map[string]GraphItem{},
Links: []GraphLink{},
}
}
func (g *Graph) Clear(id string) {
realItems := map[string]GraphItem{}
for k, it := range g.Items {
@@ -38,6 +47,10 @@ func (wf *Graph) IsProcessing(item GraphItem) bool {
return item.Processing != nil
}
func (wf *Graph) IsNativeTool(item GraphItem) bool {
return item.NativeTool != nil
}
func (wf *Graph) IsCompute(item GraphItem) bool {
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,
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)
oneIsInfinite := false
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
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 priced.GetLocationStart() != nil {
near := float64(priced.GetLocationStart().Sub(start).Seconds())
@@ -88,15 +104,16 @@ func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, proce
}
}
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
*/
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
for _, link := range g.Links { // for each link
var source string // source is the source of the link
@@ -112,13 +129,20 @@ func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingI
if r == nil { // if item is nil, 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
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
currents = append(currents, current) // append the current to the currents
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
}
var max float64 // get the max time to wait dependancies to finish
for _, current := range currents {
@@ -126,12 +150,14 @@ func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingI
max = current
}
}
return max
return max, nil
}
func (g *Graph) GetResource(id string) (tools.DataType, resources.ResourceInterface) {
if item, ok := g.Items[id]; ok {
if item.Data != nil {
return tools.NATIVE_TOOL, item.NativeTool
} else if item.Data != nil {
return tools.DATA_RESOURCE, item.Data
} else if item.Compute != nil {
return tools.COMPUTE_RESOURCE, item.Compute

View File

@@ -1,9 +1,15 @@
package workflow
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"mime/multipart"
"strings"
"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/common"
"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/workflow/graph"
"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
* 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
}
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 {
Source string
Dest string
@@ -54,6 +348,12 @@ func (w *Workflow) IsDependancy(id string) []Deps {
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) {
for _, link := range w.Graph.Links {
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(
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{}
for _, item := range w.Graph.Items {
if f(item) {
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
}
}
return list_datas
return list_datas, nil
}
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
}
if processingID == nodeID && node != nil { // if the storage is linked to the processing
if _, ok := related[processingID]; !ok {
related[processingID] = Related{}
}
rel := related[node.GetID()]
relID := node.GetID()
rel := Related{}
rel.Node = node
rel.Links = append(rel.Links, link)
related[processingID] = rel
related[relID] = rel
}
}
return related
}
func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool {
func (ao *Workflow) VerifyAuth(callName string, request *tools.APIRequest) bool {
isAuthorized := false
if len(ao.Shared) > 0 {
for _, shared := range ao.Shared {
@@ -130,11 +438,11 @@ func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool {
if code != 200 || shared == nil {
isAuthorized = false
} 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
}
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{}
ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request, wf.Graph.IsProcessing,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request)) * time.Second), priced.GetExplicitDurationInS()
}, func(started time.Time, duration float64) *time.Time {
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, error) {
d, err := wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(),
*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))
return &s
return &s, 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,
wf.Graph.IsData, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
return start, 0
}, func(started time.Time, duration float64) *time.Time {
return end
if _, priceds, err = plan[resources.ResourceInterface](tools.NATIVE_TOOL, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request,
wf.Graph.IsNativeTool, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
return start, 0, nil
}, func(started time.Time, duration float64) (*time.Time, error) {
return end, 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](k, wf, priceds, request,
f, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) {
if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, instances, partnerships, buyings, strategies, bookingMode, wf, priceds, request,
wf.Graph.IsData, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64, error) {
return start, 0, nil
}, 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) {
_, r = i.GetResource()
}
return r
}, request)
return start.Add(time.Duration(nearestStart) * time.Second), longestDuration
}, 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, 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))
return &s
return &s, nil
}); err != nil {
return 0, priceds, nil, err
return false, 0, priceds, nil, err
}
}
longest := common.GetPlannerLongestTime(end, priceds, request)
if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, wf, priceds, request, wf.Graph.IsWorkflow,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, instances, partnerships, buyings, strategies,
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)
longest := float64(-1)
r, code, err := res.GetAccessor(request).LoadOne(res.GetID())
if code != 200 || err != nil {
return start, longest
return start, longest, err
}
if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request); err != nil {
return start, longest
_, neoLongest, priceds2, _, err := r.(*Workflow).Planify(start, end, instances, partnerships, buyings, strategies, bookingMode, request)
// should ... import priced
if err != nil {
return start, longest, err
} else if neoLongest > longest {
longest = neoLongest
}
return start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second), longest
}, func(start time.Time, longest float64) *time.Time {
for k, v := range priceds2 {
if priceds[k] == nil {
priceds[k] = map[string]pricing.PricedItemITF{}
}
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
return &s, nil
}); err != nil {
return 0, priceds, nil, err
return false, 0, priceds, nil, err
}
return longest, priceds, wf, nil
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](
dt tools.DataType, 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) {
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, error),
end func(time.Time, float64) (*time.Time, error)) ([]T, map[tools.DataType]map[string]pricing.PricedItemITF, error) {
resources := []T{}
for _, item := range wf.GetGraphItems(f) {
if priceds[dt] == nil {
@@ -239,23 +617,34 @@ func plan[T resources.ResourceInterface](
if realItem == nil {
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
// if priced.SelectPricing() == nil {
// 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)
if duration >= 0 {
if e := end(started, duration); e != nil {
if e, err := end(started, duration); err == nil && e != nil {
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)
}
resources = append(resources, realItem.(T))
if priceds[dt][item.ID] != nil {
priced.AddQuantity(priceds[dt][item.ID].GetQuantity())
}
priceds[dt][item.ID] = priced
}
return resources, priceds, nil
}

View File

@@ -68,7 +68,9 @@ func (a *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *t
paccess := &peer.Peer{}
for _, p := range res.(*shallow_collaborative_area.ShallowCollaborativeArea).Peers {
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
}
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/tools"
"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) {
args := m.Called(id)
return nil, args.Int(1), args.Error(2)
@@ -93,7 +103,7 @@ func TestGetName_ReturnsCorrectFormat(t *testing.T) {
func TestVerifyAuth_AlwaysTrue(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
assert.True(t, exec.VerifyAuth(nil))
assert.True(t, exec.VerifyAuth("get", nil))
}
func TestUpdateOne_RejectsZeroState(t *testing.T) {

View File

@@ -1,6 +1,7 @@
package workflow_execution
import (
"encoding/json"
"strings"
"time"
@@ -10,6 +11,7 @@ import (
"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/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson/primitive"
@@ -22,6 +24,7 @@ import (
*/
type WorkflowExecution struct {
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
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"`
@@ -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
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
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() {
@@ -107,7 +115,7 @@ func (d *WorkflowExecution) GetAccessor(request *tools.APIRequest) utils.Accesso
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
}
@@ -141,13 +149,16 @@ func (d *WorkflowExecution) buyEach(bs pricing.BillingStrategy, executionsID str
if s := priced.GetLocationStart(); s != nil {
start = *s
}
var m map[string]interface{}
b, _ := json.Marshal(priced)
json.Unmarshal(b, &m)
end := start.Add(time.Duration(priced.GetExplicitDurationInS()) * time.Second)
bookingItem := &purchase_resource.PurchaseResource{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: d.GetName() + "_" + executionsID + "_" + wfID,
},
PricedItem: priced,
PricedItem: m,
ExecutionsID: executionsID,
DestPeerID: priced.GetCreatorID(),
ResourceID: priced.GetID(),
@@ -189,12 +200,15 @@ func (d *WorkflowExecution) bookEach(executionsID string, wfID string, dt tools.
start = *s
}
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{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: d.GetName() + "_" + executionsID + "_" + wfID,
},
PricedItem: priced,
PricedItem: m,
ExecutionsID: executionsID,
State: enum.SCHEDULED,
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
}
func (ao *Workspace) VerifyAuth(request *tools.APIRequest) bool {
func (ao *Workspace) VerifyAuth(callName string, request *tools.APIRequest) bool {
if ao.Shared != "" {
shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(ao.Shared)
if code != 200 || shared == nil {
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{}
for _, p := range res.(*shallow_collaborative_area.ShallowCollaborativeArea).Peers {
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
}
if method == tools.DELETE { // If the workspace is deleted, share the deletion

View File

@@ -16,6 +16,7 @@ type APIRequest struct {
PeerID string
Groups []string
Caller *HTTPCaller
Admin bool
}
/*
@@ -60,7 +61,9 @@ func (s State) String() string {
type API struct{}
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 {
a.SubscribeRouter(infos)
}
@@ -90,8 +93,10 @@ func (a *API) GetState() (State, int, error) {
return ALIVE, 200, nil // If everything is up, return alive
}
func (a *API) ListenRouter(exec func(msg map[string]interface{})) {
go NewNATSCaller().ListenNats(DISCOVERY.GenerateKey("api"), exec)
func (a *API) ListenRouter(exec func(msg NATSResponse)) {
go NewNATSCaller().ListenNats(map[NATSMethod]func(msg NATSResponse){
DISCOVERY: exec,
})
}
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

View File

@@ -1,5 +1,7 @@
package tools
import "strings"
type DataType int
// DataType - Enum for the different types of resources in db accessible from the outside
@@ -30,6 +32,8 @@ const (
LIVE_STORAGE
BILL
MINIO_SVCACC
MINIO_SVCACC_SECRET
NATIVE_TOOL
)
var NOAPI = ""
@@ -75,6 +79,8 @@ var DefaultAPI = [...]string{
DATACENTERAPI,
NOAPI,
MINIO,
MINIO,
CATALOGAPI,
}
// Bind the standard data name to the data type
@@ -105,6 +111,8 @@ var Str = [...]string{
"live_storage",
"bill",
"service_account",
"secret",
"native_tool",
}
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
if d < 0 {
return ""
}
return Str[d]
}
@@ -128,5 +139,45 @@ func DataTypeList() []DataType {
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,
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 (
"encoding/json"
"strings"
"sync"
"time"
"cloud.o-forge.io/core/oc-lib/config"
"cloud.o-forge.io/core/oc-lib/logs"
"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
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 (
REMOVE NATSMethod = iota
CREATE
REMOVE_EXECUTION NATSMethod = iota
CREATE_EXECTUTION
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
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)) {
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
func (d NATSMethod) GenerateKey(name string) string {
return name + "_" + d.String()
}
// String returns the string of the enum
func (d NATSMethod) String() string {
return [...]string{"remove", "create", "discovery"}[d]
func (d NATSMethod) GenerateKey() string {
return strings.ReplaceAll(d.String(), " ", "_")
}
type natsCaller struct{}
@@ -48,7 +71,7 @@ func NewNATSCaller() *natsCaller {
// on workflows' scheduling. Messages must contain
// 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()
if config.GetConfig().NATSUrl == "" {
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 {
nc, err := nats.Connect(config.GetConfig().NATSUrl)
if err != nil {
log.Error().Msg("Could not connect to NATS")
time.Sleep(1 * time.Minute)
continue
}
ch := make(chan *nats.Msg, 64)
subs, err := nc.ChanSubscribe(chanName, ch)
if err != nil {
log.Error().Msg("Error listening to NATS : " + err.Error())
}
defer subs.Unsubscribe()
for msg := range ch {
map_mess := map[string]interface{}{}
json.Unmarshal(msg.Data, &map_mess)
exec(map_mess)
defer nc.Close()
var wg sync.WaitGroup
wg.Add(len(execs))
for k, v := range execs {
go s.listenForChange(log, nc, k, v, &wg)
}
wg.Wait()
break
}
}
// 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 == "" {
return " -> NATS_SERVER is not set"
}
@@ -92,7 +111,7 @@ func (o *natsCaller) SetNATSPub(dataName string, method NATSMethod, data interfa
if err != nil {
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 {
time.Sleep(1 * time.Minute)
continue
@@ -101,3 +120,25 @@ func (o *natsCaller) SetNATSPub(dataName string, method NATSMethod, data interfa
}
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)
}
}