diff --git a/entrypoint.go b/entrypoint.go index 1e29996..7b5abe7 100644 --- a/entrypoint.go +++ b/entrypoint.go @@ -17,6 +17,7 @@ import ( "cloud.o-forge.io/core/oc-lib/models" "cloud.o-forge.io/core/oc-lib/models/collaborative_area" "cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule" + "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/resource_model" @@ -50,6 +51,7 @@ const ( COLLABORATIVE_AREA = tools.COLLABORATIVE_AREA RULE = tools.RULE BOOKING = tools.BOOKING + ORDER = tools.ORDER ) // will turn into standards api hostnames @@ -200,13 +202,12 @@ func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, If not we will store it Resource model is the model that will define the structure of the resources */ - accessor := (&resource_model.ResourceModel{}).GetAccessor("", "", []string{}, nil) + accessor := (&resource_model.ResourceModel{}).GetAccessor(nil) for _, model := range []string{tools.DATA_RESOURCE.String(), tools.PROCESSING_RESOURCE.String(), tools.STORAGE_RESOURCE.String(), tools.COMPUTE_RESOURCE.String(), tools.WORKFLOW_RESOURCE.String()} { - data, code, _ := accessor.Search(nil, model) + data, code, _ := accessor.Search(nil, model, true) if code == 404 || len(data) == 0 { refs := map[string]string{} m := map[string]resource_model.Model{} - // TODO Specify the model for each resource // for now only processing is specified here (not an elegant way) if model == tools.DATA_RESOURCE.String() || model == tools.STORAGE_RESOURCE.String() { refs["path"] = "string" @@ -279,6 +280,59 @@ func NewRequest(collection LibDataEnum, user string, peerID string, groups []str 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, start string, end string, durationInS float64, cron string) (*workflow_execution.WorkflowSchedule, error) { + scheduler := workflow_execution.NewScheduler(start, end, durationInS, cron) + if _, _, err := scheduler.Schedules(wfID, &tools.APIRequest{ + Caller: r.caller, + Username: r.user, + PeerID: r.peerID, + Groups: r.groups, + }); err != nil { + return nil, err + } + return scheduler, 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).CheckBooking(wfID, r.caller) + if err != nil { + fmt.Println(err) + return false + } + return ok +} + +func (r *Request) DraftOrder(scheduler *workflow_execution.WorkflowSchedule) (*order.Order, error) { + o := &order.Order{} + if err := o.DraftOrder(scheduler, &tools.APIRequest{ + Caller: r.caller, + Username: r.user, + PeerID: r.peerID, + Groups: r.groups, + }); err != nil { + return nil, err + } + return o, nil +} + +func (r *Request) PaymentTunnel(o *order.Order, scheduler *workflow_execution.WorkflowSchedule) error { + return o.Pay(scheduler, &tools.APIRequest{ + Caller: r.caller, + Username: r.user, + PeerID: r.peerID, + Groups: r.groups, + }) +} + /* * Search will search for the data in the database * @param filters *dbs.Filters @@ -287,14 +341,19 @@ func NewRequest(collection LibDataEnum, user string, peerID string, groups []str * @param c ...*tools.HTTPCaller * @return data LibDataShallow */ -func (r *Request) Search(filters *dbs.Filters, word string) (data LibDataShallow) { +func (r *Request) Search(filters *dbs.Filters, word string, isDraft bool) (data LibDataShallow) { defer func() { // recover the panic if r := recover(); r != nil { tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in Search : "+fmt.Sprintf("%v", r))) 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(r.user, r.peerID, r.groups, r.caller).Search(filters, word) + d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{ + Caller: r.caller, + Username: r.user, + PeerID: r.peerID, + Groups: r.groups, + }).Search(filters, word, isDraft) if err != nil { data = LibDataShallow{Data: d, Code: code, Err: err.Error()} return @@ -309,14 +368,19 @@ func (r *Request) Search(filters *dbs.Filters, word string) (data LibDataShallow * @param c ...*tools.HTTPCaller * @return data LibDataShallow */ -func (r *Request) LoadAll() (data LibDataShallow) { +func (r *Request) LoadAll(isDraft bool) (data LibDataShallow) { defer func() { // recover the panic if r := recover(); r != nil { tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in LoadAll : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack()))) data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())} } }() - d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(r.user, r.peerID, r.groups, r.caller).LoadAll() + d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{ + Caller: r.caller, + Username: r.user, + PeerID: r.peerID, + Groups: r.groups, + }).LoadAll(isDraft) if err != nil { data = LibDataShallow{Data: d, Code: code, Err: err.Error()} return @@ -339,7 +403,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(r.user, r.peerID, r.groups, r.caller).LoadOne(id) + d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{ + Caller: r.caller, + Username: r.user, + PeerID: r.peerID, + Groups: r.groups, + }).LoadOne(id) if err != nil { data = LibData{Data: d, Code: code, Err: err.Error()} return @@ -364,7 +433,12 @@ func (r *Request) UpdateOne(set map[string]interface{}, id string) (data LibData } }() model := models.Model(r.collection.EnumIndex()) - d, code, err := model.GetAccessor(r.user, r.peerID, r.groups, r.caller).UpdateOne(model.Deserialize(set, model), id) + d, code, err := model.GetAccessor(&tools.APIRequest{ + Caller: r.caller, + Username: r.user, + PeerID: r.peerID, + Groups: r.groups, + }).UpdateOne(model.Deserialize(set, model), id) if err != nil { data = LibData{Data: d, Code: code, Err: err.Error()} return @@ -387,7 +461,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(r.user, r.peerID, r.groups, r.caller).DeleteOne(id) + d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{ + Caller: r.caller, + Username: r.user, + PeerID: r.peerID, + Groups: r.groups, + }).DeleteOne(id) if err != nil { data = LibData{Data: d, Code: code, Err: err.Error()} return @@ -411,7 +490,12 @@ func (r *Request) StoreOne(object map[string]interface{}) (data LibData) { } }() model := models.Model(r.collection.EnumIndex()) - d, code, err := model.GetAccessor(r.user, r.peerID, r.groups, r.caller).StoreOne(model.Deserialize(object, model)) + d, code, err := model.GetAccessor(&tools.APIRequest{ + Caller: r.caller, + Username: r.user, + PeerID: r.peerID, + Groups: r.groups, + }).StoreOne(model.Deserialize(object, model)) if err != nil { data = LibData{Data: d, Code: code, Err: err.Error()} return @@ -435,7 +519,12 @@ func (r *Request) CopyOne(object map[string]interface{}) (data LibData) { } }() model := models.Model(r.collection.EnumIndex()) - d, code, err := model.GetAccessor(r.user, r.peerID, r.groups, r.caller).CopyOne(model.Deserialize(object, model)) + d, code, err := model.GetAccessor(&tools.APIRequest{ + Caller: r.caller, + Username: r.user, + PeerID: r.peerID, + Groups: r.groups, + }).CopyOne(model.Deserialize(object, model)) if err != nil { data = LibData{Data: d, Code: code, Err: err.Error()} return @@ -447,73 +536,80 @@ func (r *Request) CopyOne(object map[string]interface{}) (data LibData) { // ================ CAST ========================= // func (l *LibData) ToDataResource() *resources.DataResource { - if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.DATA_RESOURCE { + if l.Data.GetAccessor(nil).GetType() == tools.DATA_RESOURCE { return l.Data.(*resources.DataResource) } return nil } func (l *LibData) ToComputeResource() *resources.ComputeResource { - if l.Data != nil && l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.COMPUTE_RESOURCE { + if l.Data != nil && l.Data.GetAccessor(nil).GetType() == tools.COMPUTE_RESOURCE { return l.Data.(*resources.ComputeResource) } return nil } func (l *LibData) ToStorageResource() *resources.StorageResource { - if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.STORAGE_RESOURCE { + if l.Data.GetAccessor(nil).GetType() == tools.STORAGE_RESOURCE { return l.Data.(*resources.StorageResource) } return nil } func (l *LibData) ToProcessingResource() *resources.ProcessingResource { - if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.PROCESSING_RESOURCE { + if l.Data.GetAccessor(nil).GetType() == tools.PROCESSING_RESOURCE { return l.Data.(*resources.ProcessingResource) } return nil } func (l *LibData) ToWorkflowResource() *resources.WorkflowResource { - if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.WORKFLOW_RESOURCE { + if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW_RESOURCE { return l.Data.(*resources.WorkflowResource) } return nil } func (l *LibData) ToPeer() *peer.Peer { - if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.PEER { + if l.Data.GetAccessor(nil).GetType() == tools.PEER { return l.Data.(*peer.Peer) } return nil } func (l *LibData) ToWorkflow() *w2.Workflow { - if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.WORKFLOW { + if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW { return l.Data.(*w2.Workflow) } return nil } func (l *LibData) ToWorkspace() *workspace.Workspace { - if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.WORKSPACE { + if l.Data.GetAccessor(nil).GetType() == tools.WORKSPACE { return l.Data.(*workspace.Workspace) } return nil } func (l *LibData) ToCollaborativeArea() *collaborative_area.CollaborativeArea { - if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.COLLABORATIVE_AREA { + if l.Data.GetAccessor(nil).GetType() == tools.COLLABORATIVE_AREA { return l.Data.(*collaborative_area.CollaborativeArea) } return nil } func (l *LibData) ToRule() *rule.Rule { - if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.COLLABORATIVE_AREA { + if l.Data.GetAccessor(nil).GetType() == tools.COLLABORATIVE_AREA { return l.Data.(*rule.Rule) } return nil } -func (l *LibData) ToWorkflowExecution() *workflow_execution.WorkflowExecution { - if l.Data.GetAccessor("", "", []string{}, nil).GetType() == tools.WORKFLOW_EXECUTION { - return l.Data.(*workflow_execution.WorkflowExecution) +func (l *LibData) ToWorkflowExecution() *workflow_execution.WorkflowExecutions { + if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW_EXECUTION { + return l.Data.(*workflow_execution.WorkflowExecutions) + } + return nil +} + +func (l *LibData) ToOrder() *order.Order { + if l.Data.GetAccessor(nil).GetType() == tools.ORDER { + return l.Data.(*order.Order) } return nil } diff --git a/go.mod b/go.mod index 13ec3f4..7ab39aa 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect + github.com/biter777/countries v1.7.5 github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.4 // indirect @@ -37,6 +38,7 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/kr/text v0.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/marcinwyszynski/geopoint v0.0.0-20140302213024-cf2a6f750c5b github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index d493ed1..e406ca6 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/beego/beego/v2 v2.3.1 h1:7MUKMpJYzOXtCUsTEoXOxsDV/UcHw6CPbaWMlthVNsc= github.com/beego/beego/v2 v2.3.1/go.mod h1:5cqHsOHJIxkq44tBpRvtDe59GuVRVv/9/tyVDxd5ce4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q= +github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -53,6 +55,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/marcinwyszynski/geopoint v0.0.0-20140302213024-cf2a6f750c5b h1:XBF8THPBy28s2ryI7+/Jf/847unLWxYMpJveX5Kox+0= +github.com/marcinwyszynski/geopoint v0.0.0-20140302213024-cf2a6f750c5b/go.mod h1:z1oqhOuuYpPHmUmAK2aNygKFlPdb4o3PppQnVTRFdrI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= diff --git a/models/booking/booking.go b/models/booking/booking.go index fd19911..b67c1e8 100644 --- a/models/booking/booking.go +++ b/models/booking/booking.go @@ -4,8 +4,8 @@ import ( "time" "cloud.o-forge.io/core/oc-lib/dbs" + "cloud.o-forge.io/core/oc-lib/models/common" "cloud.o-forge.io/core/oc-lib/models/utils" - "cloud.o-forge.io/core/oc-lib/models/workflow_execution" "cloud.o-forge.io/core/oc-lib/tools" "go.mongodb.org/mongo-driver/bson/primitive" ) @@ -14,49 +14,99 @@ import ( * Booking is a struct that represents a booking */ type Booking struct { - workflow_execution.WorkflowExecution // WorkflowExecution contains the workflow execution data - ComputeResourceID string `json:"compute_resource_id,omitempty" bson:"compute_resource_id,omitempty" validate:"required"` // ComputeResourceID is the ID of the compute resource specified in the booking + utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) + DestPeerID string `json:"dest_peer_id,omitempty"` // DestPeerID is the ID of the destination peer + ExecutionID string `json:"execution_id,omitempty" bson:"execution_id,omitempty" validate:"required"` + State common.ScheduledType `json:"state,omitempty" bson:"state,omitempty" validate:"required"` // State is the state of the booking + ExpectedStartDate time.Time `json:"expected_start_date,omitempty" bson:"expected_start_date,omitempty" validate:"required"` // ExpectedStartDate is the expected start date of the booking + ExpectedEndDate *time.Time `json:"expected_end_date,omitempty" bson:"expected_end_date,omitempty" validate:"required"` // ExpectedEndDate is the expected end date of the booking + + RealStartDate *time.Time `json:"real_start_date,omitempty" bson:"real_start_date,omitempty"` // RealStartDate is the real start date of the booking + RealEndDate *time.Time `json:"real_end_date,omitempty" bson:"real_end_date,omitempty"` // RealEndDate is the real end date of the booking + + ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"` // ResourceType is the type of the resource + ResourceID string `json:"compute_resource_id,omitempty" bson:"compute_resource_id,omitempty" validate:"required"` // could be a Compute or a Storage } // CheckBooking checks if a booking is possible on a specific compute resource -func (wfa *Booking) CheckBooking(id string, start time.Time, end *time.Time) (bool, error) { +func (wfa *Booking) Check(id string, start time.Time, end *time.Time, parrallelAllowed int) (bool, error) { // check if if end == nil { // if no end... then Book like a savage - return true, nil + e := start.Add(time.Hour) + end = &e } - e := *end - accessor := New(tools.BOOKING, "", "", nil, nil) + accessor := NewAccessor(nil) res, code, err := accessor.Search(&dbs.Filters{ And: map[string][]dbs.Filter{ // check if there is a booking on the same compute resource by filtering on the compute_resource_id, the state and the execution date - "compute_resource_id": {{Operator: dbs.EQUAL.String(), Value: id}}, - "workflowexecution.state": {{Operator: dbs.EQUAL.String(), Value: workflow_execution.SCHEDULED.EnumIndex()}}, - "workflowexecution.execution_date": { - {Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(e)}, + "resource_id": {{Operator: dbs.EQUAL.String(), Value: id}}, + "state": {{Operator: dbs.EQUAL.String(), Value: common.DRAFT.EnumIndex()}}, + "expected_start_date": { + {Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(*end)}, {Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(start)}, }, }, - }, "") + }, "", wfa.IsDraft) if code != 200 { return false, err } - return len(res) == 0, nil + return len(res) <= parrallelAllowed, nil } -// tool to convert the argo status to a state -func (wfa *Booking) ArgoStatusToState(status string) *Booking { - wfa.WorkflowExecution.ArgoStatusToState(status) - return wfa +func (d *Booking) GetDelayForLaunch() time.Duration { + return d.RealStartDate.Sub(d.ExpectedStartDate) +} + +func (d *Booking) GetDelayForFinishing() time.Duration { + if d.ExpectedEndDate == nil { + return time.Duration(0) + } + return d.RealEndDate.Sub(d.ExpectedStartDate) +} + +func (d *Booking) GetUsualDuration() time.Duration { + return d.ExpectedEndDate.Sub(d.ExpectedStartDate) +} + +func (d *Booking) GetRealDuration() time.Duration { + if d.RealEndDate == nil || d.RealStartDate == nil { + return time.Duration(0) + } + return d.RealEndDate.Sub(*d.RealStartDate) +} + +func (d *Booking) GetDelayOnDuration() time.Duration { + return d.GetRealDuration() - d.GetUsualDuration() } func (d *Booking) GetName() string { - return d.UUID + "_" + d.ExecDate.String() + return d.GetID() + "_" + d.ExpectedStartDate.String() } -func (d *Booking) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New(tools.BOOKING, username, peerID, groups, caller) // Create a new instance of the accessor +func (d *Booking) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor(request) // Create a new instance of the accessor } -func (d *Booking) VerifyAuth(username string, peerID string, groups []string) bool { +func (d *Booking) VerifyAuth(request *tools.APIRequest) bool { return true } + +func (r *Booking) StoreDraftDefault() { + r.IsDraft = true +} + +func (r *Booking) CanUpdate(set utils.DBObject) (bool, utils.DBObject) { + if !r.IsDraft && r.State != set.(*Booking).State || r.RealStartDate != set.(*Booking).RealStartDate || r.RealEndDate != set.(*Booking).RealEndDate { + return true, &Booking{ + State: set.(*Booking).State, + RealStartDate: set.(*Booking).RealStartDate, + RealEndDate: set.(*Booking).RealEndDate, + } // only state can be updated + } + // TODO : HERE WE CAN HANDLE THE CASE WHERE THE BOOKING IS DELAYED OR EXCEEDING OR ending sooner + return r.IsDraft, set +} + +func (r *Booking) CanDelete() bool { + return r.IsDraft // only draft bookings can be deleted +} diff --git a/models/booking/booking_mongo_accessor.go b/models/booking/booking_mongo_accessor.go index 61aa6a8..b2831e4 100644 --- a/models/booking/booking_mongo_accessor.go +++ b/models/booking/booking_mongo_accessor.go @@ -5,8 +5,8 @@ import ( "cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/logs" + "cloud.o-forge.io/core/oc-lib/models/common" "cloud.o-forge.io/core/oc-lib/models/utils" - "cloud.o-forge.io/core/oc-lib/models/workflow_execution" "cloud.o-forge.io/core/oc-lib/tools" ) @@ -15,15 +15,12 @@ type bookingMongoAccessor struct { } // New creates a new instance of the bookingMongoAccessor -func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *bookingMongoAccessor { +func NewAccessor(request *tools.APIRequest) *bookingMongoAccessor { return &bookingMongoAccessor{ AbstractAccessor: utils.AbstractAccessor{ - Logger: logs.CreateLogger(t.String()), // Create a logger with the data type - Caller: caller, - PeerID: peerID, - Groups: groups, - User: username, // Set the caller - Type: t, + Logger: logs.CreateLogger(tools.BOOKING.String()), // Create a logger with the data type + Request: request, + Type: tools.BOOKING, }, } } @@ -49,26 +46,29 @@ func (a *bookingMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int func (a *bookingMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { return utils.GenericLoadOne[*Booking](id, func(d utils.DBObject) (utils.DBObject, int, error) { - if d.(*Booking).State == workflow_execution.SCHEDULED && time.Now().UTC().After(*d.(*Booking).ExecDate) { - d.(*Booking).State = workflow_execution.FORGOTTEN + if (d.(*Booking).ExpectedEndDate) == nil { + d.(*Booking).State = common.FORGOTTEN + utils.GenericRawUpdateOne(d, id, a) + } else if d.(*Booking).State == common.SCHEDULED && time.Now().UTC().After(*&d.(*Booking).ExpectedStartDate) { + d.(*Booking).State = common.DELAYED utils.GenericRawUpdateOne(d, id, a) } return d, 200, nil }, a) } -func (a *bookingMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { - return utils.GenericLoadAll[*Booking](a.getExec(), a) +func (a *bookingMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericLoadAll[*Booking](a.getExec(), isDraft, a) } -func (a *bookingMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { - return utils.GenericSearch[*Booking](filters, search, (&Booking{}).GetObjectFilters(search), a.getExec(), a) +func (a *bookingMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericSearch[*Booking](filters, search, (&Booking{}).GetObjectFilters(search), a.getExec(), isDraft, a) } func (a *bookingMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject { return func(d utils.DBObject) utils.ShallowDBObject { - if d.(*Booking).State == workflow_execution.SCHEDULED && time.Now().UTC().After(*d.(*Booking).ExecDate) { - d.(*Booking).State = workflow_execution.FORGOTTEN + if d.(*Booking).State == common.SCHEDULED && time.Now().UTC().After(*&d.(*Booking).ExpectedStartDate) { + d.(*Booking).State = common.DELAYED utils.GenericRawUpdateOne(d, d.GetID(), a) } return d diff --git a/models/buying_status/buying_status.go b/models/buying_status/buying_status.go new file mode 100644 index 0000000..3306e08 --- /dev/null +++ b/models/buying_status/buying_status.go @@ -0,0 +1,31 @@ +package buying_status + +import ( + "time" + + "cloud.o-forge.io/core/oc-lib/models/utils" + "cloud.o-forge.io/core/oc-lib/tools" +) + +type BuyingStatus struct { + utils.AbstractObject + BuyingDate time.Time `json:"buying_date" bson:"buying_date" validate:"required"` + EndBuyingDate *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 *BuyingStatus) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor(request) // Create a new instance of the accessor +} + +func (r *BuyingStatus) CanUpdate(set utils.DBObject) (bool, utils.DBObject) { + return r.IsDraft, set // only draft buying can be updated +} + +func (r *BuyingStatus) CanDelete() bool { // ENDBuyingDate is passed + if r.EndBuyingDate != nil { + return time.Now().UTC().After(*r.EndBuyingDate) + } + return false // only draft bookings can be deleted +} diff --git a/models/buying_status/buying_status_accessor.go b/models/buying_status/buying_status_accessor.go new file mode 100644 index 0000000..9104539 --- /dev/null +++ b/models/buying_status/buying_status_accessor.go @@ -0,0 +1,72 @@ +package buying_status + +import ( + "time" + + "cloud.o-forge.io/core/oc-lib/dbs" + "cloud.o-forge.io/core/oc-lib/logs" + "cloud.o-forge.io/core/oc-lib/models/utils" + "cloud.o-forge.io/core/oc-lib/tools" +) + +type buyingStatusMongoAccessor struct { + utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) +} + +// New creates a new instance of the bookingMongoAccessor +func NewAccessor(request *tools.APIRequest) *buyingStatusMongoAccessor { + return &buyingStatusMongoAccessor{ + AbstractAccessor: utils.AbstractAccessor{ + Logger: logs.CreateLogger(tools.BUYING_STATUS.String()), // Create a logger with the data type + Request: request, + Type: tools.BUYING_STATUS, + }, + } +} + +/* +* Nothing special here, just the basic CRUD operations + */ +func (a *buyingStatusMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { + return utils.GenericDeleteOne(id, a) +} + +func (a *buyingStatusMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { + return utils.GenericUpdateOne(set, id, a, &BuyingStatus{}) +} + +func (a *buyingStatusMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { + return utils.GenericStoreOne(data, a) +} + +func (a *buyingStatusMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { + return utils.GenericStoreOne(data, a) +} + +func (a *buyingStatusMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { + return utils.GenericLoadOne[*BuyingStatus](id, func(d utils.DBObject) (utils.DBObject, int, error) { + if d.(*BuyingStatus).EndBuyingDate != nil && time.Now().UTC().After(*d.(*BuyingStatus).EndBuyingDate) { + utils.GenericDeleteOne(id, a) + return nil, 404, nil + } + return d, 200, nil + }, a) +} + +func (a *buyingStatusMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericLoadAll[*BuyingStatus](a.getExec(), isDraft, a) +} + +func (a *buyingStatusMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericSearch[*BuyingStatus](filters, search, (&BuyingStatus{}).GetObjectFilters(search), a.getExec(), isDraft, a) +} + +func (a *buyingStatusMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject { + return func(d utils.DBObject) utils.ShallowDBObject { + if d.(*BuyingStatus).EndBuyingDate != nil && time.Now().UTC().After(*d.(*BuyingStatus).EndBuyingDate) { + utils.GenericDeleteOne(d.GetID(), a) + return nil + } + return d + } +} diff --git a/models/collaborative_area/collaborative_area.go b/models/collaborative_area/collaborative_area.go index 97ddc95..07d4918 100644 --- a/models/collaborative_area/collaborative_area.go +++ b/models/collaborative_area/collaborative_area.go @@ -69,25 +69,25 @@ func (ao *CollaborativeArea) Clear(peerID string) { ao.CollaborativeAreaRule.CreatedAt = time.Now().UTC() } -func (ao *CollaborativeArea) VerifyAuth(username string, peerID string, groups []string) bool { - if ao.AllowedPeersGroup != nil || config.GetConfig().Whitelist { - if grps, ok := ao.AllowedPeersGroup[peerID]; ok || config.GetConfig().Whitelist { +func (ao *CollaborativeArea) VerifyAuth(request *tools.APIRequest) bool { + if (ao.AllowedPeersGroup != nil || config.GetConfig().Whitelist) && request != nil { + if grps, ok := ao.AllowedPeersGroup[request.PeerID]; ok || config.GetConfig().Whitelist { fmt.Println("grps", grps, "ok", ok, "config.GetConfig().Whitelist", config.GetConfig().Whitelist) if slices.Contains(grps, "*") || (!ok && config.GetConfig().Whitelist) { return true } for _, grp := range grps { - if slices.Contains(groups, grp) { + if slices.Contains(request.Groups, grp) { return true } } } } - return ao.AbstractObject.VerifyAuth(username, peerID, groups) + return ao.AbstractObject.VerifyAuth(request) } -func (d *CollaborativeArea) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New(tools.COLLABORATIVE_AREA, username, peerID, groups, caller) // Create a new instance of the accessor +func (d *CollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor(request) // Create a new instance of the accessor } func (d *CollaborativeArea) Trim() *CollaborativeArea { diff --git a/models/collaborative_area/collaborative_area_mongo_accessor.go b/models/collaborative_area/collaborative_area_mongo_accessor.go index d67ecd5..1564d0a 100644 --- a/models/collaborative_area/collaborative_area_mongo_accessor.go +++ b/models/collaborative_area/collaborative_area_mongo_accessor.go @@ -26,19 +26,17 @@ type collaborativeAreaMongoAccessor struct { ruleAccessor utils.Accessor } -func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *collaborativeAreaMongoAccessor { +func NewAccessor(request *tools.APIRequest) *collaborativeAreaMongoAccessor { return &collaborativeAreaMongoAccessor{ AbstractAccessor: utils.AbstractAccessor{ - Logger: logs.CreateLogger(t.String()), // Create a logger with the data type - Caller: caller, - PeerID: peerID, - Groups: groups, // Set the caller - Type: t, + Logger: logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type + Request: request, + Type: tools.COLLABORATIVE_AREA, }, - workspaceAccessor: (&workspace.Workspace{}).GetAccessor(username, peerID, groups, nil), - workflowAccessor: (&w.Workflow{}).GetAccessor(username, peerID, groups, nil), - peerAccessor: (&peer.Peer{}).GetAccessor(username, peerID, groups, nil), - ruleAccessor: (&rule.Rule{}).GetAccessor(username, peerID, groups, nil), + workspaceAccessor: (&workspace.Workspace{}).GetAccessor(nil), + workflowAccessor: (&w.Workflow{}).GetAccessor(nil), + peerAccessor: (&peer.Peer{}).GetAccessor(nil), + ruleAccessor: (&rule.Rule{}).GetAccessor(nil), } } @@ -69,7 +67,7 @@ func (a *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DB _, id := (&peer.Peer{}).IsMySelf() // get the local peer data.(*CollaborativeArea).Clear(id) // set the creator // retrieve or proper peer - dd, code, err := a.peerAccessor.Search(nil, "0") + dd, code, err := a.peerAccessor.Search(nil, "0", true) if code != 200 || len(dd) == 0 { return nil, code, errors.New("Could not retrieve the peer" + err.Error()) } @@ -88,13 +86,13 @@ func (a *collaborativeAreaMongoAccessor) CopyOne(data utils.DBObject) (utils.DBO return a.StoreOne(data) } -func filterEnrich[T utils.ShallowDBObject](arr []string, a utils.Accessor) []T { +func filterEnrich[T utils.ShallowDBObject](arr []string, isDrafted bool, a utils.Accessor) []T { var new []T res, code, _ := a.Search(&dbs.Filters{ Or: map[string][]dbs.Filter{ "abstractobject.id": {{Operator: dbs.IN.String(), Value: arr}}, }, - }, "") + }, "", isDrafted) if code == 200 { for _, r := range res { new = append(new, r.(T)) @@ -104,39 +102,39 @@ func filterEnrich[T utils.ShallowDBObject](arr []string, a utils.Accessor) []T { } // enrich is a function that enriches the CollaborativeArea with the shared objects -func (a *collaborativeAreaMongoAccessor) enrich(sharedWorkspace *CollaborativeArea) *CollaborativeArea { +func (a *collaborativeAreaMongoAccessor) enrich(sharedWorkspace *CollaborativeArea, isDrafted bool) *CollaborativeArea { sharedWorkspace.SharedWorkspaces = append(sharedWorkspace.SharedWorkspaces, - filterEnrich[*workspace.Workspace](sharedWorkspace.Workspaces, a.workspaceAccessor)...) + filterEnrich[*workspace.Workspace](sharedWorkspace.Workspaces, isDrafted, a.workspaceAccessor)...) sharedWorkspace.SharedWorkflows = append(sharedWorkspace.SharedWorkflows, - filterEnrich[*workflow.Workflow](sharedWorkspace.Workflows, a.workflowAccessor)...) + filterEnrich[*workflow.Workflow](sharedWorkspace.Workflows, isDrafted, a.workflowAccessor)...) peerskey := []string{} for k := range sharedWorkspace.AllowedPeersGroup { peerskey = append(peerskey, k) } sharedWorkspace.SharedPeers = append(sharedWorkspace.SharedPeers, - filterEnrich[*peer.Peer](peerskey, a.peerAccessor)...) + filterEnrich[*peer.Peer](peerskey, isDrafted, a.peerAccessor)...) sharedWorkspace.SharedRules = append(sharedWorkspace.SharedRules, - filterEnrich[*rule.Rule](sharedWorkspace.Rules, a.ruleAccessor)...) + filterEnrich[*rule.Rule](sharedWorkspace.Rules, isDrafted, a.ruleAccessor)...) return sharedWorkspace } func (a *collaborativeAreaMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { return utils.GenericLoadOne[*CollaborativeArea](id, func(d utils.DBObject) (utils.DBObject, int, error) { - return a.enrich(d.(*CollaborativeArea)), 200, nil + return a.enrich(d.(*CollaborativeArea), true), 200, nil }, a) } -func (a *collaborativeAreaMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { +func (a *collaborativeAreaMongoAccessor) LoadAll(isDrafted bool) ([]utils.ShallowDBObject, int, error) { return utils.GenericLoadAll[*CollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject { - return a.enrich(d.(*CollaborativeArea)) - }, a) + return a.enrich(d.(*CollaborativeArea), true) + }, isDrafted, a) } -func (a *collaborativeAreaMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { +func (a *collaborativeAreaMongoAccessor) Search(filters *dbs.Filters, search string, isDrafted bool) ([]utils.ShallowDBObject, int, error) { return utils.GenericSearch[*CollaborativeArea](filters, search, (&CollaborativeArea{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { - return a.enrich(d.(*CollaborativeArea)) - }, a) + return a.enrich(d.(*CollaborativeArea), true) + }, isDrafted, a) } /* @@ -149,12 +147,12 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeAr if eld.Workspaces != nil { // update all your workspaces in the eldest by replacing shared ref by an empty string for _, v := range eld.Workspaces { a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: ""}, v) - if a.Caller != nil || a.Caller.URLS == nil || a.Caller.URLS[tools.WORKSPACE] == nil { + if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil { continue } paccess := (&peer.Peer{}) // send to all peers for k := range shared.AllowedPeersGroup { // delete the collaborative area on the peer - b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.DELETE, nil, a.Caller) + b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.DELETE, nil, a.GetCaller()) if err != nil && b == nil { a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error()) } @@ -165,7 +163,7 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeAr if shared.Workspaces != nil { for _, v := range shared.Workspaces { // update all the collaborative areas workspace, code, _ := a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: shared.UUID}, v) // add the shared ref to workspace - if a.Caller != nil || a.Caller.URLS == nil || a.Caller.URLS[tools.WORKSPACE] == nil { + if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil { continue } for k := range shared.AllowedPeersGroup { @@ -175,7 +173,7 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeAr paccess := (&peer.Peer{}) // send to all peers, add the collaborative area on the peer s := workspace.Serialize(workspace) s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k - b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.POST, s, a.Caller) + b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.POST, s, a.GetCaller()) if err != nil && b == nil { a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error()) } @@ -205,12 +203,12 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeAre n := &w.Workflow{} n.Shared = new a.workflowAccessor.UpdateOne(n, v) - if a.Caller != nil || a.Caller.URLS == nil || a.Caller.URLS[tools.WORKFLOW] == nil { + if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil { continue } paccess := (&peer.Peer{}) // send to all peers for k := range shared.AllowedPeersGroup { // delete the shared workflow on the peer - b, err := paccess.LaunchPeerExecution(k, v, tools.WORKFLOW, tools.DELETE, nil, a.Caller) + b, err := paccess.LaunchPeerExecution(k, v, tools.WORKFLOW, tools.DELETE, nil, a.GetCaller()) if err != nil && b == nil { a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error()) } @@ -227,7 +225,7 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeAre if !slices.Contains(s.Shared, id) { s.Shared = append(s.Shared, id) workflow, code, _ := a.workflowAccessor.UpdateOne(s, v) - if a.Caller != nil || a.Caller.URLS == nil || a.Caller.URLS[tools.WORKFLOW] == nil { + if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil { continue } paccess := (&peer.Peer{}) @@ -235,7 +233,7 @@ func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeAre if code == 200 { s := workflow.Serialize(workflow) // add the shared workflow on the peer s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k - b, err := paccess.LaunchPeerExecution(k, shared.UUID, tools.WORKFLOW, tools.POST, s, a.Caller) + b, err := paccess.LaunchPeerExecution(k, shared.UUID, tools.WORKFLOW, tools.POST, s, a.GetCaller()) if err != nil && b == nil { a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error()) } @@ -260,7 +258,7 @@ func (a *collaborativeAreaMongoAccessor) sendToPeer(shared *CollaborativeArea) { } func (a *collaborativeAreaMongoAccessor) contactPeer(shared *CollaborativeArea, meth tools.METHOD) { - if a.Caller == nil || a.Caller.URLS == nil || a.Caller.URLS[tools.COLLABORATIVE_AREA] == nil || a.Caller.Disabled { + if a.GetCaller() == nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.COLLABORATIVE_AREA] == nil || a.GetCaller().Disabled { return } @@ -270,7 +268,7 @@ func (a *collaborativeAreaMongoAccessor) contactPeer(shared *CollaborativeArea, continue } shared.IsSent = meth == tools.POST - b, err := paccess.LaunchPeerExecution(k, k, tools.COLLABORATIVE_AREA, meth, shared.Serialize(shared), a.Caller) + b, err := paccess.LaunchPeerExecution(k, k, tools.COLLABORATIVE_AREA, meth, shared.Serialize(shared), a.GetCaller()) if err != nil && b == nil { a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error()) } diff --git a/models/collaborative_area/rules/rule/rule.go b/models/collaborative_area/rules/rule/rule.go index e881ca7..6eff492 100644 --- a/models/collaborative_area/rules/rule/rule.go +++ b/models/collaborative_area/rules/rule/rule.go @@ -20,10 +20,10 @@ func (r *Rule) GenerateID() { r.UUID = uuid.New().String() } -func (d *Rule) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New(tools.RULE, username, peerID, groups, caller) +func (d *Rule) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor(request) } -func (d *Rule) VerifyAuth(username string, peerID string, groups []string) bool { +func (d *Rule) VerifyAuth(request *tools.APIRequest) bool { return true } diff --git a/models/collaborative_area/rules/rule/rule_mongo_accessor.go b/models/collaborative_area/rules/rule/rule_mongo_accessor.go index eea81cf..4f8773b 100644 --- a/models/collaborative_area/rules/rule/rule_mongo_accessor.go +++ b/models/collaborative_area/rules/rule/rule_mongo_accessor.go @@ -2,7 +2,6 @@ package rule import ( "cloud.o-forge.io/core/oc-lib/dbs" - "cloud.o-forge.io/core/oc-lib/dbs/mongo" "cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" @@ -13,89 +12,51 @@ type ruleMongoAccessor struct { } // New creates a new instance of the ruleMongoAccessor -func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *ruleMongoAccessor { +func NewAccessor(request *tools.APIRequest) *ruleMongoAccessor { return &ruleMongoAccessor{ AbstractAccessor: utils.AbstractAccessor{ - Logger: logs.CreateLogger(t.String()), // Create a logger with the data type - Caller: caller, - PeerID: peerID, - Groups: groups, // Set the caller - User: username, - Type: t, + Logger: logs.CreateLogger(tools.RULE.String()), // Create a logger with the data type + Request: request, + Type: tools.RULE, }, } } -// GetType returns the type of the rule +/* +* Nothing special here, just the basic CRUD operations + */ func (a *ruleMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { return utils.GenericDeleteOne(id, a) } -// UpdateOne updates a rule in the database func (a *ruleMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { - return utils.GenericUpdateOne(set.(*Rule), id, a, &Rule{}) + return utils.GenericUpdateOne(set, id, a, &Rule{}) } -// StoreOne stores a rule in the database func (a *ruleMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { - return utils.GenericStoreOne(data.(*Rule), a) + return utils.GenericStoreOne(data, a) } func (a *ruleMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { return utils.GenericStoreOne(data, a) } -// LoadOne loads a rule from the database func (a *ruleMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { - var rule Rule - res_mongo, code, err := mongo.MONGOService.LoadOne(id, a.GetType().String()) - if err != nil { - a.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error()) - return nil, code, err - } - res_mongo.Decode(&rule) - return &rule, 200, nil + return utils.GenericLoadOne[*Rule](id, func(d utils.DBObject) (utils.DBObject, int, error) { + return d, 200, nil + }, a) } -// LoadAll loads all rules from the database -func (a ruleMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { - objs := []utils.ShallowDBObject{} - res_mongo, code, err := mongo.MONGOService.LoadAll(a.GetType().String()) - if err != nil { - a.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error()) - return nil, code, err - } - var results []Rule - if err = res_mongo.All(mongo.MngoCtx, &results); err != nil { - return nil, 404, err - } - for _, r := range results { - objs = append(objs, &r) - } - return objs, 200, nil +func (a *ruleMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericLoadAll[*Rule](a.getExec(), isDraft, a) } -// Search searches for rules in the database, given some filters OR a search string -func (a *ruleMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { - objs := []utils.ShallowDBObject{} - if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" { - filters = &dbs.Filters{ - Or: map[string][]dbs.Filter{ // filter by name if no filters are provided - "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}}, - }, - } - } - res_mongo, code, err := mongo.MONGOService.Search(filters, a.GetType().String()) - if err != nil { - a.Logger.Error().Msg("Could not store to db. Error: " + err.Error()) - return nil, code, err - } - var results []Rule - if err = res_mongo.All(mongo.MngoCtx, &results); err != nil { - return nil, 404, err - } - for _, r := range results { - objs = append(objs, &r) - } - return objs, 200, nil +func (a *ruleMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericSearch[*Rule](filters, search, (&Rule{}).GetObjectFilters(search), a.getExec(), isDraft, a) +} + +func (a *ruleMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject { + return func(d utils.DBObject) utils.ShallowDBObject { + return d + } } diff --git a/models/collaborative_area/shallow_collaborative_area/shallow_collaborative_area.go b/models/collaborative_area/shallow_collaborative_area/shallow_collaborative_area.go index 5d07b23..aa60cd0 100644 --- a/models/collaborative_area/shallow_collaborative_area/shallow_collaborative_area.go +++ b/models/collaborative_area/shallow_collaborative_area/shallow_collaborative_area.go @@ -17,6 +17,6 @@ type ShallowCollaborativeArea struct { Rules []string `json:"rules,omitempty" bson:"rules,omitempty"` } -func (d *ShallowCollaborativeArea) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New(tools.COLLABORATIVE_AREA, username, peerID, groups, caller) +func (d *ShallowCollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor(request) } diff --git a/models/collaborative_area/shallow_collaborative_area/shallow_collaborative_area_mongo_accessor.go b/models/collaborative_area/shallow_collaborative_area/shallow_collaborative_area_mongo_accessor.go index 1873271..bc070fc 100644 --- a/models/collaborative_area/shallow_collaborative_area/shallow_collaborative_area_mongo_accessor.go +++ b/models/collaborative_area/shallow_collaborative_area/shallow_collaborative_area_mongo_accessor.go @@ -11,15 +11,12 @@ type shallowSharedWorkspaceMongoAccessor struct { utils.AbstractAccessor } -func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *shallowSharedWorkspaceMongoAccessor { +func NewAccessor(request *tools.APIRequest) *shallowSharedWorkspaceMongoAccessor { return &shallowSharedWorkspaceMongoAccessor{ AbstractAccessor: utils.AbstractAccessor{ - Logger: logs.CreateLogger(t.String()), // Create a logger with the data type - Caller: caller, - PeerID: peerID, - User: username, // Set the caller - Groups: groups, // Set the caller - Type: t, + Logger: logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type + Request: request, // Set the caller + Type: tools.COLLABORATIVE_AREA, }, } } @@ -46,14 +43,14 @@ func (a *shallowSharedWorkspaceMongoAccessor) LoadOne(id string) (utils.DBObject }, a) } -func (a *shallowSharedWorkspaceMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { +func (a *shallowSharedWorkspaceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { return utils.GenericLoadAll[*ShallowCollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject { return d - }, a) + }, isDraft, a) } -func (a *shallowSharedWorkspaceMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { +func (a *shallowSharedWorkspaceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { return utils.GenericSearch[*ShallowCollaborativeArea](filters, search, (&ShallowCollaborativeArea{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { return d - }, a) + }, isDraft, a) } diff --git a/models/common/access_configuration.go b/models/common/access_configuration.go new file mode 100644 index 0000000..6df6174 --- /dev/null +++ b/models/common/access_configuration.go @@ -0,0 +1,17 @@ +package common + +type Container struct { + Image string `json:"image,omitempty" bson:"image,omitempty"` // Image is the container image TEMPO + Command string `json:"command,omitempty" bson:"command,omitempty"` // Command is the container command + Args string `json:"args,omitempty" bson:"args,omitempty"` // Args is the container arguments + Env map[string]string `json:"env,omitempty" bson:"env,omitempty"` // Env is the container environment variables + Volumes map[string]string `json:"volumes,omitempty" bson:"volumes,omitempty"` // Volumes is the container volumes + + Exposes []Expose `bson:"exposes,omitempty" json:"exposes,omitempty"` // Expose is the execution +} + +type Expose struct { + Port int `json:"port,omitempty" bson:"port,omitempty"` // Port is the port + Reverse string `json:"reverse,omitempty" bson:"reverse,omitempty"` // Reverse is the reverse + PAT int `json:"pat,omitempty" bson:"pat,omitempty"` // PAT is the PAT +} diff --git a/models/common/devices.go b/models/common/devices.go new file mode 100644 index 0000000..392f6ff --- /dev/null +++ b/models/common/devices.go @@ -0,0 +1,33 @@ +package common + +// CPU is a struct that represents a CPU +type CPU struct { + Model string `bson:"platform,omitempty" json:"platform,omitempty"` + FrequencyGhz float64 `bson:"frenquency,omitempty" json:"frenquency,omitempty"` + Cores int `bson:"cores,omitempty" json:"cores,omitempty"` + Architecture string `bson:"architecture,omitempty" json:"architecture,omitempty"` +} + +type RAM struct { + SizeGb float64 `bson:"size,omitempty" json:"size,omitempty" description:"Units in MB"` + Ecc bool `bson:"ecc" json:"ecc" default:"true"` +} + +type GPU struct { + Model string `bson:"platform,omitempty" json:"platform,omitempty"` + MemoryGb float64 `bson:"memory,omitempty" json:"memory,omitempty" description:"Units in MB"` +} + +type InfrastructureType int + +const ( + DOCKER InfrastructureType = iota + KUBERNETES + SLURM + HW + CONDOR +) + +func (t InfrastructureType) String() string { + return [...]string{"DOCKER", "KUBERNETES", "SLURM", "HW", "CONDOR"}[t] +} diff --git a/models/common/pricing/interfaces.go b/models/common/pricing/interfaces.go new file mode 100644 index 0000000..e245ecf --- /dev/null +++ b/models/common/pricing/interfaces.go @@ -0,0 +1,17 @@ +package pricing + +import ( + "time" + + "cloud.o-forge.io/core/oc-lib/tools" +) + +type PricedItemITF interface { + GetID() string + GetType() tools.DataType + IsBuying(request *tools.APIRequest) bool + GetCreatorID() string + GetLocationStart() *time.Time + GetLocationEnd() *time.Time + GetPrice(request *tools.APIRequest) (float64, error) +} diff --git a/models/common/pricing/pricing_profile.go b/models/common/pricing/pricing_profile.go new file mode 100644 index 0000000..89fbb58 --- /dev/null +++ b/models/common/pricing/pricing_profile.go @@ -0,0 +1,56 @@ +package pricing + +import ( + "time" + + "cloud.o-forge.io/core/oc-lib/models/utils" + "cloud.o-forge.io/core/oc-lib/tools" +) + +type PricingProfileITF interface { + GetID() string + GetPrice(quantity float64, val float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) + IsBuying() bool + GetOverrideStrategyValue() int +} + +type RefundType int + +const ( + REFUND_DEAD_END RefundType = iota + REFUND_ON_ERROR + REFUND_ON_EARLY_END +) + +type AccessPricingProfile struct { // only use for acces such as : DATA && PROCESSING + utils.DBObject + DefaultRefund RefundType `json:"default_refund" bson:"default_refund"` // DefaultRefund is the default refund type of the pricing + RefundRatio int32 `json:"refund_ratio" bson:"refund_ratio" default:"0"` // RefundRatio is the refund ratio if missing +} + +func (b *AccessPricingProfile) GetOverrideStrategyValue() int { + return -1 +} + +type ExploitPricingStrategy int + +const ( + BASIC ExploitPricingStrategy = iota + GARANTED_ON_DELAY + GARANTED +) + +func (t ExploitPricingStrategy) String() string { + return [...]string{"BASIC", "GARANTED_ON_DELAY", "GARANTED"}[t] +} + +type ExploitPricingProfile struct { // only use for exploit such as : STORAGE, COMPUTE, WORKFLOW + AccessPricingProfile + AdditionnalRefundTypes []RefundType `json:"refund_types" bson:"refund_types"` // RefundTypes is the refund types of the pricing + + PrivilegeStrategy ExploitPricingStrategy `json:"privilege_strategy,omitempty" bson:"privilege_strategy,omitempty"` // Strategy is the strategy of the pricing + GarantedDelaySecond uint + + Exceeding bool + ExceedingRatio int32 `json:"exceeding_ratio" bson:"exceeding_ratio" default:"0"` // ExceedingRatio is the exceeding ratio of the bill +} diff --git a/models/common/pricing/pricing_strategy.go b/models/common/pricing/pricing_strategy.go new file mode 100644 index 0000000..8ea7f02 --- /dev/null +++ b/models/common/pricing/pricing_strategy.go @@ -0,0 +1,136 @@ +package pricing + +import ( + "errors" + "fmt" + "strconv" + "time" +) + +type BuyingStrategy int + +const ( + UNLIMITED BuyingStrategy = iota + SUBSCRIPTION + PAY_PER_USE +) + +type Strategy interface { + GetStrategy() string + GetStrategyValue() int +} + +type TimePricingStrategy int + +const ( + ONCE TimePricingStrategy = iota + PER_SECOND + PER_MINUTE + PER_HOUR + PER_DAY + PER_WEEK + PER_MONTH +) + +func (t TimePricingStrategy) GetStrategy() string { + return [...]string{"ONCE", "PER_SECOND", "PER_MINUTE", "PER_HOUR", "PER_DAY", "PER_WEEK", "PER_MONTH"}[t] +} + +func (t TimePricingStrategy) GetStrategyValue() int { + return int(t) +} + +func getAverageTimeInSecond(averageTimeInSecond float64, start time.Time, end *time.Time) float64 { + now := time.Now() + after := now.Add(time.Duration(averageTimeInSecond) * time.Second) + + fromAverageDuration := after.Sub(now).Seconds() + var tEnd time.Time + if end == nil { + tEnd = start.Add(1 * time.Hour) + } else { + tEnd = *end + } + fromDateDuration := tEnd.Sub(start).Seconds() + + if fromAverageDuration > fromDateDuration { + return fromAverageDuration + } + return fromDateDuration +} + +func BookingEstimation(t TimePricingStrategy, price float64, locationDurationInSecond float64, start time.Time, end *time.Time) (float64, error) { + locationDurationInSecond = getAverageTimeInSecond(locationDurationInSecond, start, end) + priceStr := fmt.Sprintf("%v", price) + p, err := strconv.ParseFloat(priceStr, 64) + if err != nil { + return 0, err + } + switch t { + case ONCE: + return p, nil + case PER_HOUR: + return p * float64(locationDurationInSecond/3600), nil + case PER_MINUTE: + return p * float64(locationDurationInSecond/60), nil + case PER_SECOND: + return p * locationDurationInSecond, nil + case PER_DAY: + return p * float64(locationDurationInSecond/86400), nil + case PER_WEEK: + return p * float64(locationDurationInSecond/604800), nil + case PER_MONTH: + return p * float64(locationDurationInSecond/2592000), nil + } + return 0, errors.New("Pricing strategy not found") +} + +// hmmmm +type PricingStrategy[T Strategy] struct { + Price float64 `json:"Price" bson:"Price" default:"0"` // Price is the Price of the pricing + BuyingStrategy BuyingStrategy `json:"buying_strategy" bson:"buying_strategy" default:"0"` // BuyingStrategy is the buying strategy of the pricing + TimePricingStrategy TimePricingStrategy `json:"time_pricing_strategy" bson:"time_pricing_strategy" default:"0"` // TimePricingStrategy is the time pricing strategy of the pricing + OverrideStrategy T `json:"override_strategy" bson:"override_strategy" default:"-1"` // Modulation is the modulation of the pricing +} + +func (p PricingStrategy[T]) SetStrategy(Price float64, BuyingStrategy BuyingStrategy, TimePricingStrategy TimePricingStrategy) error { + if TimePricingStrategy == ONCE && (BuyingStrategy != UNLIMITED || BuyingStrategy != PAY_PER_USE) { + return errors.New("time pricing strategy can only be set to ONCE if buying strategy is UNLIMITED or PAY_PER_USE") + } else if BuyingStrategy == SUBSCRIPTION && (TimePricingStrategy == ONCE) { + return errors.New("subscription duration in second must be set if buying strategy is SUBSCRIPTION") + } + p.Price = Price + p.BuyingStrategy = BuyingStrategy + p.TimePricingStrategy = TimePricingStrategy + return nil +} + +func (p PricingStrategy[T]) SetSpecificPerUseStrategy(strategy T) error { + if p.BuyingStrategy == UNLIMITED { + return errors.New("UNLIMITED buying strategy can't have a specific strategy, Price is set on buying") + } + p.OverrideStrategy = strategy + return nil +} + +// QUANTITY can be how many of gb core per example +func (p PricingStrategy[T]) GetPrice(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time) (float64, error) { + if p.BuyingStrategy == SUBSCRIPTION { + return BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end) + } else if p.BuyingStrategy == UNLIMITED { + return p.Price, nil + } + return p.Price * float64(amountOfData), nil +} + +func (p PricingStrategy[T]) GetBuyingStrategy() BuyingStrategy { + return p.BuyingStrategy +} + +func (p PricingStrategy[T]) GetTimePricingStrategy() TimePricingStrategy { + return p.TimePricingStrategy +} + +func (p PricingStrategy[T]) GetOverrideStrategy() T { + return p.OverrideStrategy +} diff --git a/models/common/schedule_type.go b/models/common/schedule_type.go new file mode 100644 index 0000000..86c7d1a --- /dev/null +++ b/models/common/schedule_type.go @@ -0,0 +1,38 @@ +package common + +type ScheduledType int + +const ( + DRAFT ScheduledType = iota + SCHEDULED + STARTED + FAILURE + SUCCESS + FORGOTTEN + DELAYED + CANCELLED +) + +var str = [...]string{ + "draft", + "scheduled", + "started", + "failure", + "success", + "forgotten", + "delayed", + "cancelled", +} + +func FromInt(i int) string { + return str[i] +} + +func (d ScheduledType) String() string { + return str[d] +} + +// EnumIndex - Creating common behavior - give the type a EnumIndex functio +func (d ScheduledType) EnumIndex() int { + return int(d) +} diff --git a/models/common/size.go b/models/common/size.go new file mode 100644 index 0000000..ce05984 --- /dev/null +++ b/models/common/size.go @@ -0,0 +1,34 @@ +package common + +type StorageSize int + +// StorageType - Enum that defines the type of storage +const ( + GB StorageSize = iota + MB + KB +) + +var argoType = [...]string{ + "Gi", + "Mi", + "Ki", +} + +// New creates a new instance of the StorageResource struct +func (dma StorageSize) ToArgo() string { + return argoType[dma] +} + +// enum of a data type +type StorageType int + +const ( + FILE = iota + STREAM + API + DATABASE + S3 + MEMORY + HARDWARE +) diff --git a/models/models.go b/models/models.go index 682459a..1793248 100644 --- a/models/models.go +++ b/models/models.go @@ -2,6 +2,7 @@ package models import ( "cloud.o-forge.io/core/oc-lib/logs" + "cloud.o-forge.io/core/oc-lib/models/order" "cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/models/booking" @@ -27,7 +28,7 @@ var models = map[string]func() utils.DBObject{ tools.STORAGE_RESOURCE.String(): func() utils.DBObject { return &resource.StorageResource{} }, tools.PROCESSING_RESOURCE.String(): func() utils.DBObject { return &resource.ProcessingResource{} }, tools.WORKFLOW.String(): func() utils.DBObject { return &w2.Workflow{} }, - tools.WORKFLOW_EXECUTION.String(): func() utils.DBObject { return &workflow_execution.WorkflowExecution{} }, + tools.WORKFLOW_EXECUTION.String(): func() utils.DBObject { return &workflow_execution.WorkflowExecutions{} }, tools.WORKSPACE.String(): func() utils.DBObject { return &w3.Workspace{} }, tools.RESOURCE_MODEL.String(): func() utils.DBObject { return &resource_model.ResourceModel{} }, tools.PEER.String(): func() utils.DBObject { return &peer.Peer{} }, @@ -36,6 +37,7 @@ var models = map[string]func() utils.DBObject{ tools.BOOKING.String(): func() utils.DBObject { return &booking.Booking{} }, tools.WORKFLOW_HISTORY.String(): func() utils.DBObject { return &w2.WorkflowHistory{} }, tools.WORKSPACE_HISTORY.String(): func() utils.DBObject { return &w3.WorkspaceHistory{} }, + tools.ORDER.String(): func() utils.DBObject { return &order.Order{} }, } // Model returns the model object based on the model type diff --git a/models/order/order.go b/models/order/order.go new file mode 100644 index 0000000..505b90e --- /dev/null +++ b/models/order/order.go @@ -0,0 +1,333 @@ +package order + +import ( + "errors" + "fmt" + "sync" + "time" + + "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/buying_status" + "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/models/workflow_execution" + "cloud.o-forge.io/core/oc-lib/tools" +) + +/* +* Booking is a struct that represents a booking + */ + +type OrderStatus = int + +const ( + DRAFT OrderStatus = iota + PENDING + CANCELLED + PARTIAL + PAID + DISPUTED + OVERDUE + REFUND +) + +type Order struct { + utils.AbstractObject + OrderBy string `json:"order_by" bson:"order_by" validate:"required"` + WorkflowExecutionIDs []string `json:"workflow_execution_ids" bson:"workflow_execution_ids" validate:"required"` + Status OrderStatus `json:"status" bson:"status" default:"0"` + SubOrders map[string]*PeerOrder `json:"sub_orders" bson:"sub_orders"` + Total float64 `json:"total" bson:"total" validate:"required"` +} + +func (r *Order) StoreDraftDefault() { + r.IsDraft = true +} + +func (r *Order) CanUpdate(set utils.DBObject) (bool, utils.DBObject) { + if !r.IsDraft && r.Status != set.(*Order).Status { + return true, &Order{Status: set.(*Order).Status} // only state can be updated + } + return r.IsDraft, set +} + +func (r *Order) CanDelete() bool { + return r.IsDraft // only draft order can be deleted +} + +func (o *Order) DraftOrder(scheduler *workflow_execution.WorkflowSchedule, request *tools.APIRequest) error { + // set the draft order from the model + if err := o.draftStoreFromModel(scheduler, request); err != nil { + return err + } + return nil +} + +func (o *Order) Pay(scheduler *workflow_execution.WorkflowSchedule, request *tools.APIRequest) error { + if _, err := o.draftBookOrder(scheduler, request); err != nil { + return err + } + o.Status = PENDING + _, code, err := o.GetAccessor(request).UpdateOne(o, o.GetID()) + if code != 200 || err != nil { + return errors.New("could not update the order" + fmt.Sprintf("%v", err)) + } + if err := o.pay(request); err != nil { // pay the order + return err + } else { + o.IsDraft = false + } + for _, exec := range scheduler.WorkflowExecutions { + exec.IsDraft = false + _, code, err := utils.GenericUpdateOne(exec, exec.GetID(), + workflow_execution.NewAccessor(request), &workflow_execution.WorkflowExecutions{}) + if code != 200 || err != nil { + return errors.New("could not update the workflow execution" + fmt.Sprintf("%v", err)) + } + } + _, code, err = o.GetAccessor(request).UpdateOne(o, o.GetID()) + if code != 200 || err != nil { + return errors.New("could not update the order" + fmt.Sprintf("%v", err)) + } + /* + TODO : TEMPORARY SET BOOKINGS TO UNDRAFT TO AVOID DELETION + BUT NEXT ONLY WHO IS PAYED WILL BE ALLOWED TO CHANGE IT + */ + return nil +} + +func (o *Order) draftStoreFromModel(scheduler *workflow_execution.WorkflowSchedule, request *tools.APIRequest) error { + if request == nil { + return errors.New("no request found") + } + if scheduler.Workflow.Graph == nil { // if the workflow has no graph, return an error + return errors.New("no graph found") + } + o.SetName() + o.IsDraft = true + o.OrderBy = request.Username + o.WorkflowExecutionIDs = []string{} // create an array of ids + for _, exec := range scheduler.WorkflowExecutions { + o.WorkflowExecutionIDs = append(o.WorkflowExecutionIDs, exec.GetID()) + } + // set the name of the order + resourcesByPeer := map[string][]pricing.PricedItemITF{} // create a map of resources by peer + + processings := scheduler.Workflow.GetPricedItem(scheduler.Workflow.IsProcessing) // get the processing items + datas := scheduler.Workflow.GetPricedItem(scheduler.Workflow.IsData) // get the data items + storages := scheduler.Workflow.GetPricedItem(scheduler.Workflow.IsStorage) // get the storage items + workflows := scheduler.Workflow.GetPricedItem(scheduler.Workflow.IsWorkflow) // get the workflow items + for _, items := range []map[string]pricing.PricedItemITF{processings, datas, storages, workflows} { + for _, item := range items { + if _, ok := resourcesByPeer[item.GetCreatorID()]; !ok { + resourcesByPeer[item.GetCreatorID()] = []pricing.PricedItemITF{} + } + resourcesByPeer[item.GetCreatorID()] = append(resourcesByPeer[item.GetCreatorID()], item) + } + } + for peerID, resources := range resourcesByPeer { + peerOrder := &PeerOrder{ + Status: DRAFT, + PeerID: peerID, + } + peerOrder.GenerateID() + for _, resource := range resources { + peerOrder.AddItem(resource, len(scheduler.WorkflowExecutions)) // TODO SPECIALS REF ADDITIONALS NOTES + } + o.SubOrders[peerOrder.GetID()] = peerOrder + } + // search an order with same user name and same session id + err := o.sumUpBill(request) + if err != nil { + return err + } + // should store the order + res, code, err := o.GetAccessor(request).Search(&dbs.Filters{ + And: map[string][]dbs.Filter{ + "order_by": {{Operator: dbs.EQUAL.String(), Value: request.Username}}, + }, + }, "", o.IsDraft) + if code != 200 || err != nil { + return errors.New("could not search the order" + fmt.Sprintf("%v", err)) + } + if len(res) > 0 { + _, code, err := utils.GenericUpdateOne(o, res[0].GetID(), o.GetAccessor(request), o) + if code != 200 || err != nil { + return errors.New("could not update the order" + fmt.Sprintf("%v", err)) + } + } else { + _, code, err := utils.GenericStoreOne(o, o.GetAccessor(request)) + if code != 200 || err != nil { + return errors.New("could not store the order" + fmt.Sprintf("%v", err)) + } + } + return nil +} + +func (o *Order) draftBookOrder(scheduler *workflow_execution.WorkflowSchedule, request *tools.APIRequest) ([]*booking.Booking, error) { + draftedBookings := []*booking.Booking{} + if request == nil { + return draftedBookings, errors.New("no request found") + } + for _, exec := range scheduler.WorkflowExecutions { + bookings := exec.ToBookings(scheduler.Workflow) + for _, booking := range bookings { + _, err := (&peer.Peer{}).LaunchPeerExecution(booking.DestPeerID, "", + tools.BOOKING, tools.POST, booking.Serialize(booking), request.Caller) + if err != nil { + return draftedBookings, errors.New("could not launch the peer execution : " + fmt.Sprintf("%v", err)) + } + draftedBookings = append(draftedBookings, booking) + } + } + return draftedBookings, nil +} + +func (o *Order) Quantity() int { + return len(o.WorkflowExecutionIDs) +} + +func (d *Order) SetName() { + d.Name = d.UUID + "_order_" + "_" + time.Now().UTC().Format("2006-01-02T15:04:05") +} + +func (d *Order) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor(request) // Create a new instance of the accessor +} + +func (d *Order) sumUpBill(request *tools.APIRequest) error { + for _, b := range d.SubOrders { + err := b.SumUpBill(request) + if err != nil { + return err + } + d.Total += b.Total + } + return nil +} + +// TO FINISH +func (d *Order) pay(request *tools.APIRequest) error { + responses := make(chan *PeerOrder, len(d.SubOrders)) + var wg *sync.WaitGroup + wg.Add(len(d.SubOrders)) + for _, b := range d.SubOrders { + go b.Pay(request, responses, wg) + } + wg.Wait() + errs := "" + gotAnUnpaid := false + count := 0 + for range responses { + res := <-responses + count++ + if res != nil { + if res.Error != "" { + errs += res.Error + } + if res.Status != PAID { + gotAnUnpaid = true + } + d.Status = PARTIAL + d.SubOrders[res.GetID()] = res + if count == len(d.SubOrders) && !gotAnUnpaid { + d.Status = PAID + } + } + } + + if errs != "" { + return errors.New(errs) + } + return nil +} + +type PeerOrder struct { + utils.AbstractObject + Error string `json:"error,omitempty" bson:"error,omitempty"` + PeerID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"` + Status OrderStatus `json:"status" bson:"status" default:"0"` + BillingAddress string `json:"billing_address,omitempty" bson:"billing_address,omitempty"` + Items []*PeerItemOrder `json:"items,omitempty" bson:"items,omitempty"` + Total float64 `json:"total,omitempty" bson:"total,omitempty"` +} + +func (d *PeerOrder) Pay(request *tools.APIRequest, response chan *PeerOrder, wg *sync.WaitGroup) { + d.Status = PENDING + go func() { + // DO SOMETHING TO PAY ON BLOCKCHAIN OR WHATEVER ON RETURN UPDATE STATUS + d.Status = PAID // TO REMOVE LATER IT'S A MOCK + if d.Status == PAID { + for _, b := range d.Items { + if !b.Item.IsBuying(request) { + continue + } + accessor := buying_status.NewAccessor(request) + accessor.StoreOne(&buying_status.BuyingStatus{ + BuyingDate: time.Now(), + ResourceID: b.Item.GetID(), + ResourceType: b.Item.GetType(), + EndBuyingDate: b.Item.GetLocationEnd(), + }) + } + } + + if d.Status != PENDING { + response <- d + } + wg.Done() + }() +} + +func (d *PeerOrder) SumUpBill(request *tools.APIRequest) error { + for _, b := range d.Items { + tot, err := b.GetPrice(request) // missing something + if err != nil { + return err + } + d.Total += tot + } + return nil +} + +func (d *PeerOrder) AddItem(item pricing.PricedItemITF, quantity int) { + d.Items = append(d.Items, &PeerItemOrder{ + Quantity: quantity, + Item: item, + }) +} + +func (d *PeerOrder) SetName() { + d.Name = d.UUID + "_order_" + d.PeerID + "_" + time.Now().UTC().Format("2006-01-02T15:04:05") +} + +type PeerItemOrder struct { + Quantity int `json:"quantity,omitempty" bson:"quantity,omitempty"` + BuyingStatus buying_status.BuyingStatus `json:"buying_status,omitempty" bson:"buying_status,omitempty"` + Item pricing.PricedItemITF `json:"item,omitempty" bson:"item,omitempty"` +} + +func (d *PeerItemOrder) GetPrice(request *tools.APIRequest) (float64, error) { + accessor := buying_status.NewAccessor(request) + search, code, _ := accessor.Search(&dbs.Filters{ + And: map[string][]dbs.Filter{ + "resource_id": {{Operator: dbs.EQUAL.String(), Value: d.Item.GetID()}}, + }, + }, "", d.BuyingStatus.IsDraft) + if code == 200 && len(search) > 0 { + for _, s := range search { + if s.(*buying_status.BuyingStatus).EndBuyingDate == nil || time.Now().UTC().After(*s.(*buying_status.BuyingStatus).EndBuyingDate) { + return 0, nil + } + } + } + p, err := d.Item.GetPrice(request) + if err != nil { + return 0, err + } + return p * float64(d.Quantity), nil +} + +// SHOULD SET A BUYING STATUS WHEN PAYMENT IS VALIDATED diff --git a/models/order/order_mongo_accessor.go b/models/order/order_mongo_accessor.go new file mode 100644 index 0000000..adbf43f --- /dev/null +++ b/models/order/order_mongo_accessor.go @@ -0,0 +1,64 @@ +package order + +import ( + "errors" + + "cloud.o-forge.io/core/oc-lib/dbs" + "cloud.o-forge.io/core/oc-lib/logs" + "cloud.o-forge.io/core/oc-lib/models/utils" + "cloud.o-forge.io/core/oc-lib/tools" +) + +type orderMongoAccessor struct { + utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) +} + +// New creates a new instance of the orderMongoAccessor +func NewAccessor(request *tools.APIRequest) *orderMongoAccessor { + return &orderMongoAccessor{ + AbstractAccessor: utils.AbstractAccessor{ + Logger: logs.CreateLogger(tools.ORDER.String()), // Create a logger with the data type + Request: request, + Type: tools.ORDER, + }, + } +} + +/* +* Nothing special here, just the basic CRUD operations + */ +func (a *orderMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { + return utils.GenericDeleteOne(id, a) +} + +func (a *orderMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { + return utils.GenericUpdateOne(set, id, a, &Order{}) +} + +func (a *orderMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { + return nil, 404, errors.New("Not implemented") +} + +func (a *orderMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { + return nil, 404, errors.New("Not implemented") +} + +func (a *orderMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { + return utils.GenericLoadOne[*Order](id, func(d utils.DBObject) (utils.DBObject, int, error) { + return d, 200, nil + }, a) +} + +func (a *orderMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericLoadAll[*Order](a.getExec(), isDraft, a) +} + +func (a *orderMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericSearch[*Order](filters, search, (&Order{}).GetObjectFilters(search), a.getExec(), isDraft, a) +} + +func (a *orderMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject { + return func(d utils.DBObject) utils.ShallowDBObject { + return d + } +} diff --git a/models/peer/peer.go b/models/peer/peer.go index 8032545..a8f4ac0 100644 --- a/models/peer/peer.go +++ b/models/peer/peer.go @@ -29,10 +29,11 @@ func (m PeerState) EnumIndex() int { // Peer is a struct that represents a peer type Peer struct { utils.AbstractObject - Url string `json:"url,omitempty" bson:"url,omitempty" validate:"required"` // Url is the URL of the peer (base64url) - PublicKey string `json:"public_key,omitempty" bson:"public_key,omitempty"` // PublicKey is the public key of the peer - Services map[string]int `json:"services,omitempty" bson:"services,omitempty"` + Url string `json:"url" bson:"url" validate:"required"` // Url is the URL of the peer (base64url) + 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"` + 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 } @@ -62,13 +63,13 @@ func (ao *Peer) RemoveExecution(exec PeerExecution) { } // IsMySelf checks if the peer is the local peer -func (ao *Peer) IsMySelf() (bool, string) { - d, code, err := New(tools.PEER, "", "", nil, nil).Search(nil, SELF.String()) +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 ao.UUID == id, id + return p.UUID == id, id } // LaunchPeerExecution launches an execution on a peer @@ -76,11 +77,11 @@ func (p *Peer) LaunchPeerExecution(peerID string, dataID string, dt tools.DataTy p.UUID = peerID return cache.LaunchPeerExecution(peerID, dataID, dt, method, body, caller) // Launch the execution on the peer through the cache } -func (d *Peer) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - data := New(tools.PEER, username, peerID, groups, caller) // Create a new instance of the accessor +func (d *Peer) GetAccessor(request *tools.APIRequest) utils.Accessor { + data := NewAccessor(request) // Create a new instance of the accessor return data } -func (d *Peer) VerifyAuth(username string, peerID string, groups []string) bool { - return true +func (r *Peer) CanDelete() bool { + return false // only draft order can be deleted } diff --git a/models/peer/peer_cache.go b/models/peer/peer_cache.go index 8a5bae4..6482400 100644 --- a/models/peer/peer_cache.go +++ b/models/peer/peer_cache.go @@ -56,7 +56,7 @@ func (p *PeerCache) urlFormat(url string, dt tools.DataType) string { // checkPeerStatus checks the status of a peer func (p *PeerCache) checkPeerStatus(peerID string, appName string, caller *tools.HTTPCaller) (*Peer, bool) { api := tools.API{} - access := NewShallow() + access := NewShallowAccessor() res, code, _ := access.LoadOne(peerID) // Load the peer from db if code != 200 { // no peer no party return nil, false @@ -73,7 +73,7 @@ func (p *PeerCache) checkPeerStatus(peerID string, appName string, caller *tools fmt.Println("Checking peer status on", url, "...") state, services := api.CheckRemotePeer(url) fmt.Println("Checking peer status on", url, state, services) // Check the status of the peer - res.(*Peer).Services = services // Update the services states of the peer + res.(*Peer).ServicesState = services // Update the services states of the peer access.UpdateOne(res, peerID) // Update the peer in the db return res.(*Peer), state != tools.DEAD && services[appName] == 0 // Return the peer and its status } @@ -101,18 +101,18 @@ func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string, DataID: dataID, } mypeer.AddExecution(*pexec) - NewShallow().UpdateOne(mypeer, peerID) // Update the peer in the db + NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db return nil, errors.New("peer is not reachable") } else { if mypeer == nil { return nil, errors.New("peer not found") } // If the peer is reachable, launch the execution - url = p.urlFormat((mypeer.Url)+meth, dt) // Format the URL - tmp := mypeer.FailedExecution // Get the failed executions list - mypeer.FailedExecution = []PeerExecution{} // Reset the failed executions list - NewShallow().UpdateOne(mypeer, peerID) // Update the peer in the db - for _, v := range tmp { // Retry the failed executions + url = p.urlFormat((mypeer.Url)+meth, dt) // 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 + for _, v := range tmp { // Retry the failed executions go p.exec(v.Url, tools.ToMethod(v.Method), v.Body, caller) } } diff --git a/models/peer/peer_mongo_accessor.go b/models/peer/peer_mongo_accessor.go index c79080a..d90bcb6 100644 --- a/models/peer/peer_mongo_accessor.go +++ b/models/peer/peer_mongo_accessor.go @@ -14,7 +14,7 @@ type peerMongoAccessor struct { } // New creates a new instance of the peerMongoAccessor -func NewShallow() *peerMongoAccessor { +func NewShallowAccessor() *peerMongoAccessor { return &peerMongoAccessor{ utils.AbstractAccessor{ Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type @@ -23,15 +23,12 @@ func NewShallow() *peerMongoAccessor { } } -func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *peerMongoAccessor { +func NewAccessor(request *tools.APIRequest) *peerMongoAccessor { return &peerMongoAccessor{ utils.AbstractAccessor{ - Logger: logs.CreateLogger(t.String()), // Create a logger with the data type - Caller: caller, - PeerID: peerID, - User: username, - Groups: groups, // Set the caller - Type: t, + Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type + Request: request, + Type: tools.PEER, }, } } @@ -62,17 +59,17 @@ func (dca *peerMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { }, dca) } -func (wfa *peerMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { +func (wfa *peerMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { return utils.GenericLoadAll[*Peer](func(d utils.DBObject) utils.ShallowDBObject { return d - }, wfa) + }, isDraft, wfa) } -func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { +func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { return utils.GenericSearch[*Peer](filters, search, wfa.getDefaultFilter(search), func(d utils.DBObject) utils.ShallowDBObject { return d - }, wfa) + }, isDraft, wfa) } func (a *peerMongoAccessor) getDefaultFilter(search string) *dbs.Filters { s, err := strconv.Atoi(search) diff --git a/models/resources/compute.go b/models/resources/compute.go index d464954..a3a7981 100644 --- a/models/resources/compute.go +++ b/models/resources/compute.go @@ -1,7 +1,12 @@ package resources import ( - "cloud.o-forge.io/core/oc-lib/models/resources/resource_model" + "errors" + "strings" + "time" + + "cloud.o-forge.io/core/oc-lib/models/common" + "cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" ) @@ -11,68 +16,171 @@ import ( * it defines the resource compute */ type ComputeResource struct { - resource_model.AbstractResource - Technology TechnologyEnum `json:"technology" bson:"technology" default:"0"` // Technology is the technology - Architecture string `json:"architecture,omitempty" bson:"architecture,omitempty"` // Architecture is the architecture - Access AccessEnum `json:"access" bson:"access" default:"0"` // Access is the access - - Localisation string `json:"localisation,omitempty" bson:"localisation,omitempty"` // Localisation is the localisation - - CPUs []*CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs - RAM *RAM `bson:"ram,omitempty" json:"ram,omitempty"` // RAM is the RAM - GPUs []*GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs + AbstractResource[*ComputeResourceInstance] + Architecture string `json:"architecture,omitempty" bson:"architecture,omitempty"` // Architecture is the architecture + Infrastructure common.InfrastructureType `json:"infrastructure,omitempty" bson:"infrastructure,omitempty"` } -func (d *ComputeResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New[*ComputeResource](tools.COMPUTE_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &ComputeResource{} }) +func (d *ComputeResource) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor[*ComputeResource](tools.COMPUTE_RESOURCE, request, func() utils.DBObject { return &ComputeResource{} }) } -// CPU is a struct that represents a CPU -type CPU struct { - Cores uint `bson:"cores,omitempty" json:"cores,omitempty"` //TODO: validate - Architecture string `bson:"architecture,omitempty" json:"architecture,omitempty"` //TOOD: enum - Shared bool `bson:"shared,omitempty" json:"shared,omitempty"` - MinimumMemory uint `bson:"minimum_memory,omitempty" json:"minimum_memory,omitempty"` - Platform string `bson:"platform,omitempty" json:"platform,omitempty"` +type ComputeResourceInstance struct { + ResourceInstance[*ComputeResourcePartnership] + SecurityLevel string `json:"security_level,omitempty" bson:"security_level,omitempty"` + PowerSource string `json:"power_source,omitempty" bson:"power_source,omitempty"` + CPUs map[string]*common.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model + GPUs map[string]*common.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model + RAM *common.RAM `bson:"ram,omitempty" json:"ram,omitempty"` // RAM is the RAM } -type RAM struct { - Size uint `bson:"size,omitempty" json:"size,omitempty" description:"Units in MB"` - Ecc bool `bson:"ecc,omitempty" json:"ecc,omitempty"` +type ComputeResourcePartnership struct { + ResourcePartnerShip[*ComputeResourcePricingProfile] + MaxAllowedCPUsCores map[string]int `json:"allowed_cpus,omitempty" bson:"allowed_cpus,omitempty"` + MaxAllowedGPUsMemoryGB map[string]float64 `json:"allowed_gpus,omitempty" bson:"allowed_gpus,omitempty"` + MaxAllowedRAMSize float64 `json:"allowed_ram,omitempty" bson:"allowed_ram,omitempty"` } -type GPU struct { - CudaCores uint `bson:"cuda_cores,omitempty" json:"cuda_cores,omitempty"` - Model string `bson:"model,omitempty" json:"model,omitempty"` - Memory uint `bson:"memory,omitempty" json:"memory,omitempty" description:"Units in MB"` - TensorCores uint `bson:"tensor_cores,omitempty" json:"tensor_cores,omitempty"` +func (r *ComputeResourceInstance) VerifyPartnerships() bool { + peersMultiple := map[string]int{} + for _, p := range r.Partnerships { + for k, g := range p.PeerGroups { + for _, v := range g { + if _, ok := peersMultiple[k+"_"+v]; !ok { + peersMultiple[k + "_" + v] = 0 + } + peersMultiple[k+"_"+v]++ + } + } + for k, p := range peersMultiple { + if p > 1 { + return false + } + } } -type TechnologyEnum int - -const ( - DOCKER TechnologyEnum = iota - KUBERNETES - SLURM - HW - CONDOR -) - -func (t TechnologyEnum) String() string { - return [...]string{"DOCKER", "KUBERNETES", "SLURM", "HW", "CONDOR"}[t] +type ComputeResourcePricingProfileOptions struct { + CPUCore int `json:"cpu_core" bson:"cpu_core" default:"1"` + GPUMemoryGB float64 `json:"gpu_memory_gb" bson:"gpu_memory_gb" default:"1"` + RAMSizeGB float64 `json:"ram_size_gb" bson:"ram_size_gb" default:"1"` } -type AccessEnum int - -const ( - SSH AccessEnum = iota - SSH_KUBE_API - SSH_SLURM - SSH_DOCKER - OPENCLOUD - VPN -) - -func (a AccessEnum) String() string { - return [...]string{"SSH", "SSH_KUBE_API", "SSH_SLURM", "SSH_DOCKER", "OPENCLOUD", "VPN"}[a] +type ComputeResourcePricingProfile struct { + pricing.ExploitPricingProfile + Pricing pricing.PricingStrategy[pricing.TimePricingStrategy] `json:"price,omitempty" bson:"price,omitempty"` // Price is the price of the resource + Options ComputeResourcePricingProfileOptions `json:"options,omitempty" bson:"options,omitempty"` // Options is the options of the pricing profile + // ExploitPricingProfile is the pricing profile of a compute it means that we exploit the resource for an amount of continuous time + OverrideCPUsPrices map[string]float64 `json:"cpus_prices,omitempty" bson:"cpus_prices,omitempty"` // CPUsPrices is the prices of the CPUs + OverrideGPUsPrices map[string]float64 `json:"gpus_prices,omitempty" bson:"gpus_prices,omitempty"` // GPUsPrices is the prices of the GPUs + OverrideRAMPrice float64 `json:"ram_price" bson:"ram_price" default:"-1"` // RAMPrice is the price of the RAM +} + +// PROBLEM + +func (p *ComputeResourcePricingProfile) IsBuying() bool { + return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE +} + +func (p *ComputeResourcePricingProfile) GetOverrideStrategyValue() int { + return -1 +} + +// 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, request *tools.APIRequest, params ...string) (float64, error) { + if len(params) < 1 { + return 0, errors.New("params must be set") + } + pp := float64(0) + model := params[1] + if strings.Contains(params[0], "cpus") && len(params) > 1 { + if _, ok := p.OverrideCPUsPrices[model]; ok { + p.Pricing.Price = p.OverrideCPUsPrices[model] + } + r, err := p.Pricing.GetPrice(amountOfData/float64(p.Options.CPUCore), explicitDuration, start, &end) + if err != nil { + return 0, err + } + pp += r + + } + if strings.Contains(params[0], "gpus") && len(params) > 1 { + if _, ok := p.OverrideGPUsPrices[model]; ok { + p.Pricing.Price = p.OverrideGPUsPrices[model] + } + r, err := p.Pricing.GetPrice(amountOfData/float64(p.Options.GPUMemoryGB), explicitDuration, start, &end) + if err != nil { + return 0, err + } + pp += r + } + if strings.Contains(params[0], "ram") { + if p.OverrideRAMPrice >= 0 { + p.Pricing.Price = p.OverrideRAMPrice + } + r, err := p.Pricing.GetPrice(float64(amountOfData)/p.Options.RAMSizeGB, explicitDuration, start, &end) + if err != nil { + return 0, err + } + pp += r + } + return pp, nil +} + +type CustomizedComputeResource struct { + AbstractCustomizedResource[*ComputeResourceInstance] + + CPUsLocated map[string]float64 `json:"cpus_in_use" bson:"cpus_in_use"` // CPUsInUse is the list of CPUs in use + GPUsLocated map[string]float64 `json:"gpus_in_use" bson:"gpus_in_use"` // GPUsInUse is the list of GPUs in use + RAMLocated float64 `json:"ram_in_use" bson:"ram_in_use"` // RAMInUse is the RAM in use +} + +func (r *CustomizedComputeResource) GetType() tools.DataType { + return tools.COMPUTE_RESOURCE +} + +func (r *CustomizedComputeResource) GetPrice(request *tools.APIRequest) (float64, error) { + if r.UsageStart == nil || r.UsageEnd == nil { + return 0, errors.New("Usage start and end must be set") + } + partner := r.GetPartnership(request) + if partner != nil && partner.GetPricing(r.SelectedPricing) != nil { + return 0, errors.New("Pricing strategy not found") + } + pricing := partner.GetPricing(r.SelectedPricing) + 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, request, "cpus", model) + if err != nil { + return 0, err + } + price += cpus + } + } + ram, err := pricing.GetPrice(r.RAMLocated, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, request, "ram") + if err != nil { + return 0, err + } + price += ram + return price, nil +} + +/* +* FillWithDefaultProcessingUsage fills the order item with the default processing usage +* it depends on the processing usage only if nothing is set, during order + */ +func (i *CustomizedComputeResource) FillWithDefaultProcessingUsage(usage *ProcessingUsage) { + for _, cpu := range usage.CPUs { + if _, ok := i.CPUsLocated[cpu.Model]; !ok { + i.CPUsLocated[cpu.Model] = 0 + } + if i.CPUsLocated[cpu.Model] < float64(cpu.Cores) { + i.CPUsLocated[cpu.Model] = float64(cpu.Cores) + } + } + for _, cpu := range usage.GPUs { + i.GPUsLocated[cpu.Model] = 1 + } + i.RAMLocated = usage.RAM.SizeGb } diff --git a/models/resources/data.go b/models/resources/data.go index dbb31f9..d65570f 100644 --- a/models/resources/data.go +++ b/models/resources/data.go @@ -1,7 +1,10 @@ package resources import ( - "cloud.o-forge.io/core/oc-lib/models/resources/resource_model" + "errors" + "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" ) @@ -15,12 +18,15 @@ const ( LICENCED ) -/* -* Struct of Usage Conditions - */ -type UsageConditions struct { - Usage string `json:"usage,omitempty" bson:"usage,omitempty" description:"usage of the data"` // Usage is the usage of the data - Actors []string `json:"actors,omitempty" bson:"actors,omitempty" description:"actors of the data"` // Actors is the actors of the data +type abstractDataResource struct { + Type string `bson:"type,omitempty" json:"type,omitempty"` + Quality string `bson:"quality,omitempty" json:"quality,omitempty"` + OpenData bool `bson:"open_data" json:"open_data" default:"false"` // Type is the type of the storage + Static bool `bson:"static" json:"static" default:"false"` + UpdatePeriod time.Time `bson:"update_period,omitempty" json:"update_period,omitempty"` + PersonalData bool `bson:"personal_data,omitempty" json:"personal_data,omitempty"` + AnonymizedPersonalData bool `bson:"anonymized_personal_data,omitempty" json:"anonymized_personal_data,omitempty"` + SizeGB float64 `json:"size_gb,omitempty" bson:"size_gb,omitempty"` // SizeGB is the size of the data } /* @@ -28,15 +34,105 @@ type UsageConditions struct { * it defines the resource data */ type DataResource struct { - resource_model.AbstractResource // AbstractResource contains the basic fields of an object (id, name) - resource_model.WebResource - Type string `bson:"type,omitempty" json:"type,omitempty"` // Type is the type of the storage - UsageConditions UsageConditions `json:"usage_conditions,omitempty" bson:"usage_conditions,omitempty" description:"usage conditions of the data"` // UsageConditions is the usage conditions of the data - License DataLicense `json:"license" bson:"license" description:"license of the data" default:"0"` // License is the license of the data - Interest DataLicense `json:"interest" bson:"interest" description:"interest of the data" default:"0"` // Interest is the interest of the data - Example string `json:"example,omitempty" bson:"example,omitempty" description:"base64 encoded data"` // Example is an example of the data + AbstractResource[*ResourceInstance[*DataResourcePartnership]] + abstractDataResource // AbstractResource contains the basic fields of an object (id, name) + License DataLicense `json:"license" bson:"license" description:"license of the data" default:"0"` // License is the license of the data + // ? Interest DataLicense `json:"interest" bson:"interest" description:"interest of the data" default:"0"` // Interest is the interest of the data + Example string `json:"example,omitempty" bson:"example,omitempty" description:"base64 encoded data"` // Example is an example of the data } -func (d *DataResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New[*DataResource](tools.DATA_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &DataResource{} }) // Create a new instance of the accessor +func (d *DataResource) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor[*DataResource](tools.DATA_RESOURCE, request, func() utils.DBObject { return &DataResource{} }) // Create a new instance of the accessor +} + +type DataResourcePartnership struct { + ResourcePartnerShip[*DataResourcePricingProfile] + MaxDownloadableGbAllowed float64 `json:"allowed_gb,omitempty" bson:"allowed_gb,omitempty"` + PersonalDataAllowed bool `json:"personal_data_allowed,omitempty" bson:"personal_data_allowed,omitempty"` + AnonymizedPersonalDataAllowed bool `json:"anonymized_personal_data_allowed,omitempty" bson:"anonymized_personal_data_allowed,omitempty"` +} + +type DataResourcePricingStrategy int + +const ( + PER_DOWNLOAD DataResourcePricingStrategy = iota + PER_TB_DOWNLOADED + PER_GB_DOWNLOADED + PER_MB_DOWNLOADED + PER_KB_DOWNLOADED +) + +func ToDataResourcePricingStrategy(i int) DataResourcePricingStrategy { + return DataResourcePricingStrategy(i) +} + +func (t DataResourcePricingStrategy) GetStrategy() string { + return [...]string{"PER_DOWNLOAD", "PER_GB", "PER_MB", "PER_KB"}[t] +} + +func (t DataResourcePricingStrategy) GetStrategyValue() int { + return int(t) +} + +func (t DataResourcePricingStrategy) GetQuantity(amountOfDataGB float64) (float64, error) { + switch t { + case PER_DOWNLOAD: + return 1, nil + case PER_TB_DOWNLOADED: + return amountOfDataGB * 1000, nil + case PER_GB_DOWNLOADED: + return amountOfDataGB, nil + case PER_MB_DOWNLOADED: + return amountOfDataGB / 1000, nil + case PER_KB_DOWNLOADED: + return amountOfDataGB / 1000000, nil + } + return 0, errors.New("Pricing strategy not found") +} + +type DataResourcePricingProfile struct { + Pricing *pricing.PricingStrategy[DataResourcePricingStrategy] `json:"cpus_prices,omitempty" bson:"cpus_prices,omitempty"` // CPUsPrices is the prices of the CPUs + pricing.AccessPricingProfile // AccessPricingProfile is the pricing profile of a data it means that we can access the data for an amount of time +} + +func (p *DataResourcePricingProfile) GetOverrideStrategyValue() int { + return p.Pricing.OverrideStrategy.GetStrategyValue() +} + +func (p *DataResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) { + return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end) +} + +func (p *DataResourcePricingProfile) IsBuying() bool { + return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE +} + +type CustomizedDataResource struct { + AbstractCustomizedResource[*ResourceInstance[*DataResourcePartnership]] + abstractDataResource + StorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"` +} + +func (r *CustomizedDataResource) GetType() tools.DataType { + return tools.DATA_RESOURCE +} + +func (r *CustomizedDataResource) GetPrice(request *tools.APIRequest) (float64, error) { + if r.UsageStart == nil || r.UsageEnd == nil { + return 0, errors.New("Usage start and end must be set") + } + partner := r.GetPartnership(request) + if partner != nil && partner.GetPricing(r.SelectedPricing) != nil { + return 0, errors.New("Pricing strategy not found") + } + pricing := partner.GetPricing(r.SelectedPricing) + var err error + amountOfData := float64(1) + if pricing.GetOverrideStrategyValue() >= 0 { + amountOfData, err = ToDataResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.StorageGB) + if err != nil { + return 0, err + } + } + return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, request) } diff --git a/models/resources/interfaces.go b/models/resources/interfaces.go new file mode 100644 index 0000000..035e2fd --- /dev/null +++ b/models/resources/interfaces.go @@ -0,0 +1,47 @@ +package resources + +import ( + "time" + + "cloud.o-forge.io/core/oc-lib/models/common/pricing" + "cloud.o-forge.io/core/oc-lib/models/resources/resource_model" + "cloud.o-forge.io/core/oc-lib/models/utils" + "cloud.o-forge.io/core/oc-lib/tools" +) + +type ShallowResourceInterface interface { + utils.DBObject + GetType() tools.DataType + GetCreatorID() string + GetPricingID() string + GetLocationStart() *time.Time + GetLocationEnd() *time.Time + GetExplicitDurationInS() float64 + SetStartUsage(start time.Time) + SetEndUsage(end time.Time) + GetPartnership(request *tools.APIRequest) ResourcePartnerITF + SetResourceModel(model *resource_model.ResourceModel) +} + +type ResourceInterface interface { + utils.DBObject + Trim() + GetCreatorID() string + VerifyPartnerships() bool + GetPartnership(request *tools.APIRequest) ResourcePartnerITF + SetAllowedInstances(request *tools.APIRequest) + SetResourceModel(model *resource_model.ResourceModel) +} + +type InstanceITF interface { + GetID() string + VerifyPartnerships() bool + GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) + ClearPeerGroups() +} + +type ResourcePartnerITF interface { + GetPricing(id string) pricing.PricingProfileITF + GetPeerGroups() map[string][]string + ClearPeerGroups() +} diff --git a/models/resources/models.go b/models/resources/models.go new file mode 100644 index 0000000..47d9bc3 --- /dev/null +++ b/models/resources/models.go @@ -0,0 +1,92 @@ +package resources + +import ( + "time" + + "cloud.o-forge.io/core/oc-lib/models/utils" + "cloud.o-forge.io/core/oc-lib/tools" +) + +type ExploitedResourceSet struct { + DataResources []*CustomizedDataResource `bson:"-" json:"data_resources,omitempty"` + StorageResources []*CustomizedStorageResource `bson:"-" json:"storage_resources,omitempty"` + ProcessingResources []*CustomizedProcessingResource `bson:"-" json:"processing_resources,omitempty"` + ComputeResources []*CustomizedComputeResource `bson:"-" json:"compute_resources,omitempty"` + WorkflowResources []*CustomizedWorkflowResource `bson:"-" json:"workflow_resources,omitempty"` +} + +type ResourceSet struct { + Datas []string `bson:"datas,omitempty" json:"datas,omitempty"` + Storages []string `bson:"storages,omitempty" json:"storages,omitempty"` + Processings []string `bson:"processings,omitempty" json:"processings,omitempty"` + Computes []string `bson:"computes,omitempty" json:"computes,omitempty"` + Workflows []string `bson:"workflows,omitempty" json:"workflows,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"` +} + +func (r *ResourceSet) Clear() { + r.DataResources = nil + r.StorageResources = nil + r.ProcessingResources = nil + r.ComputeResources = nil + r.WorkflowResources = nil +} + +func (r *ResourceSet) Fill(request *tools.APIRequest) { + for k, v := range map[utils.DBObject][]string{ + (&DataResource{}): r.Datas, + (&ComputeResource{}): r.Computes, + (&StorageResource{}): r.Storages, + (&ProcessingResource{}): r.Processings, + (&WorkflowResource{}): r.Workflows, + } { + for _, id := range v { + d, _, e := k.GetAccessor(request).LoadOne(id) + if e == nil { + switch k.(type) { + case *DataResource: + r.DataResources = append(r.DataResources, d.(*DataResource)) + case *ComputeResource: + r.ComputeResources = append(r.ComputeResources, d.(*ComputeResource)) + case *StorageResource: + r.StorageResources = append(r.StorageResources, d.(*StorageResource)) + case *ProcessingResource: + r.ProcessingResources = append(r.ProcessingResources, d.(*ProcessingResource)) + case *WorkflowResource: + r.WorkflowResources = append(r.WorkflowResources, d.(*WorkflowResource)) + } + } + } + } +} + +type ItemExploitedResource struct { + Data *CustomizedDataResource `bson:"data,omitempty" json:"data,omitempty"` + Processing *CustomizedProcessingResource `bson:"processing,omitempty" json:"processing,omitempty"` + Storage *CustomizedStorageResource `bson:"storage,omitempty" json:"storage,omitempty"` + Compute *CustomizedComputeResource `bson:"compute,omitempty" json:"compute,omitempty"` + Workflow *CustomizedWorkflowResource `bson:"workflow,omitempty" json:"workflow,omitempty"` +} + +func (w *ItemExploitedResource) SetItemEndUsage(end time.Time) { + for _, item := range []ShallowResourceInterface{w.Data, w.Processing, w.Storage, w.Compute, w.Workflow} { + if item != nil { + item.SetItemEndUsage(end) + } + + } +} + +func (w *ItemExploitedResource) SetItemStartUsage(start time.Time) { + for _, item := range []ShallowResourceInterface{w.Data, w.Processing, w.Storage, w.Compute, w.Workflow} { + if item != nil { + item.SetItemStartUsage(start) + } + + } +} diff --git a/models/resources/processing.go b/models/resources/processing.go index f09b471..4985e3d 100644 --- a/models/resources/processing.go +++ b/models/resources/processing.go @@ -1,23 +1,28 @@ package resources import ( - "cloud.o-forge.io/core/oc-lib/models/resources/resource_model" + "time" + + "cloud.o-forge.io/core/oc-lib/models/common" + "cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" ) -type Container struct { - Image string `json:"image,omitempty" bson:"image,omitempty"` // Image is the container image - Command string `json:"command,omitempty" bson:"command,omitempty"` // Command is the container command - Args string `json:"args,omitempty" bson:"args,omitempty"` // Args is the container arguments - Env map[string]string `json:"env,omitempty" bson:"env,omitempty"` // Env is the container environment variables - Volumes map[string]string `json:"volumes,omitempty" bson:"volumes,omitempty"` // Volumes is the container volumes +type ProcessingUsage struct { + CPUs map[string]*common.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model + GPUs map[string]*common.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model + RAM *common.RAM `bson:"ram,omitempty" json:"ram,omitempty"` // RAM is the RAM + + StorageGb float64 `bson:"storage,omitempty" json:"storage,omitempty"` // Storage is the storage + Hypothesis string `bson:"hypothesis,omitempty" json:"hypothesis,omitempty"` + ScalingModel string `bson:"scaling_model,omitempty" json:"scaling_model,omitempty"` // ScalingModel is the scaling model } -type Expose struct { - Port int `json:"port,omitempty" bson:"port,omitempty"` // Port is the port - Reverse string `json:"reverse,omitempty" bson:"reverse,omitempty"` // Reverse is the reverse - PAT int `json:"pat,omitempty" bson:"pat,omitempty"` // PAT is the PAT +type abstractProcessingResource struct { + Infrastructure common.InfrastructureType `json:"infrastructure,omitempty" bson:"infrastructure,omitempty"` + Service 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 } /* @@ -25,19 +30,49 @@ type Expose struct { * it defines the resource processing */ type ProcessingResource struct { - resource_model.AbstractResource - IsService bool `json:"is_service,omitempty" bson:"is_service,omitempty"` // IsService is a flag that indicates if the processing is a service - CPUs []*CPU `bson:"cpus,omitempty" json:"cp_us,omitempty"` // CPUs is the list of CPUs - GPUs []*GPU `bson:"gpus,omitempty" json:"gp_us,omitempty"` // GPUs is the list of GPUs - RAM *RAM `bson:"ram,omitempty" json:"ram,omitempty"` // RAM is the RAM - Storage uint `bson:"storage,omitempty" json:"storage,omitempty"` // Storage is the storage - Parallel bool `bson:"parallel,omitempty" json:"parallel,omitempty"` // Parallel is a flag that indicates if the processing is parallel - ScalingModel uint `bson:"scaling_model,omitempty" json:"scaling_model,omitempty"` // ScalingModel is the scaling model - DiskIO string `bson:"disk_io,omitempty" json:"disk_io,omitempty"` // DiskIO is the disk IO - Container *Container `bson:"container,omitempty" json:"container,omitempty"` // Container is the container - Expose []Expose `bson:"expose,omitempty" json:"expose,omitempty"` // Expose is the execution + AbstractResource[*ResourceInstance[*ResourcePartnerShip[*ProcessingResourcePricingProfile]]] + abstractProcessingResource + OpenSource bool `json:"open_source" bson:"open_source" default:"false"` + License string `json:"license,omitempty" bson:"license,omitempty"` + Maturity string `json:"maturity,omitempty" bson:"maturity,omitempty"` } -func (d *ProcessingResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New[*ProcessingResource](tools.PROCESSING_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &ProcessingResource{} }) // Create a new instance of the accessor +type CustomizedProcessingResource struct { + AbstractCustomizedResource[*ResourceInstance[*ResourcePartnerShip[*ProcessingResourcePricingProfile]]] + abstractProcessingResource + Container common.Container `json:"container,omitempty" bson:"container,omitempty"` // Container is the container +} + +func (r *CustomizedProcessingResource) GetType() tools.DataType { + return tools.PROCESSING_RESOURCE +} + +func (a *CustomizedProcessingResource) GetExplicitDurationInS() float64 { + if a.ExplicitBookingDurationS == 0 { + if a.UsageEnd == nil || a.UsageStart == nil { + if a.Service { + return -1 + } + return time.Duration(1 * time.Hour).Seconds() + } + return a.UsageEnd.Sub(*a.UsageStart).Seconds() + } + return a.ExplicitBookingDurationS +} + +func (d *ProcessingResource) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor[*ProcessingResource](tools.PROCESSING_RESOURCE, request, func() utils.DBObject { return &ProcessingResource{} }) // Create a new instance of the accessor +} + +type ProcessingResourcePricingProfile struct { + Pricing *pricing.PricingStrategy[pricing.TimePricingStrategy] `json:"cpus_prices,omitempty" bson:"cpus_prices,omitempty"` // CPUsPrices is the prices of the CPUs + pricing.AccessPricingProfile // AccessPricingProfile is the pricing profile of a data it means that we can access the data for an amount of time +} + +func (p *ProcessingResourcePricingProfile) IsBuying() bool { + return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE +} + +func (p *ProcessingResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) { + return p.Pricing.GetPrice(amountOfData, val, start, &end) } diff --git a/models/resources/resource.go b/models/resources/resource.go index 45785ea..f9d8f91 100644 --- a/models/resources/resource.go +++ b/models/resources/resource.go @@ -1,8 +1,18 @@ package resources import ( + "errors" + "slices" + "time" + + "cloud.o-forge.io/core/oc-lib/config" + "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/resources/resource_model" "cloud.o-forge.io/core/oc-lib/models/utils" + "cloud.o-forge.io/core/oc-lib/tools" + "github.com/biter777/countries" + "github.com/marcinwyszynski/geopoint" ) // AbstractResource is the struct containing all of the attributes commons to all ressources @@ -10,86 +20,265 @@ import ( // Resource is the interface to be implemented by all classes inheriting from Resource to have the same behavior // http://www.inanzzz.com/index.php/post/wqbs/a-basic-usage-of-int-and-string-enum-types-in-golang -type ResourceInterface interface { - utils.DBObject - Trim() *resource_model.AbstractResource - SetResourceModel(model *resource_model.ResourceModel) +/* +* AbstractResource is a struct that represents a resource +* it defines the resource data + */ +type abstractResource struct { + utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) + 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 + ResourceModel *resource_model.ResourceModel `json:"resource_model,omitempty" bson:"resource_model,omitempty"` // ResourceModel is the model of the resource + UsageRestrictions string `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"` } -type ResourceSet struct { - Datas []string `bson:"datas,omitempty" json:"datas,omitempty"` - Storages []string `bson:"storages,omitempty" json:"storages,omitempty"` - Processings []string `bson:"processings,omitempty" json:"processings,omitempty"` - Computes []string `bson:"computes,omitempty" json:"computes,omitempty"` - Workflows []string `bson:"workflows,omitempty" json:"workflows,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"` +func (r *abstractResource) StoreDraftDefault() { + r.IsDraft = true } -func (r *ResourceSet) Clear() { - r.DataResources = nil - r.StorageResources = nil - r.ProcessingResources = nil - r.ComputeResources = nil - r.WorkflowResources = nil +func (r *AbstractCustomizedResource[T]) CanUpdate(set utils.DBObject) (bool, utils.DBObject) { + if r.IsDraft != set.IsDrafted() && set.IsDrafted() { + return true, set // only state can be updated + } + return r.IsDraft != set.IsDrafted() && set.IsDrafted(), set } -func (r *ResourceSet) Fill(username string, peerID string, groups []string) { - for k, v := range map[utils.DBObject][]string{ - (&DataResource{}): r.Datas, - (&ComputeResource{}): r.Computes, - (&StorageResource{}): r.Storages, - (&ProcessingResource{}): r.Processings, - (&WorkflowResource{}): r.Workflows, - } { - for _, id := range v { - d, _, e := k.GetAccessor(username, peerID, groups, nil).LoadOne(id) - if e == nil { - switch k.(type) { - case *DataResource: - r.DataResources = append(r.DataResources, d.(*DataResource)) - case *ComputeResource: - r.ComputeResources = append(r.ComputeResources, d.(*ComputeResource)) - case *StorageResource: - r.StorageResources = append(r.StorageResources, d.(*StorageResource)) - case *ProcessingResource: - r.ProcessingResources = append(r.ProcessingResources, d.(*ProcessingResource)) - case *WorkflowResource: - r.WorkflowResources = append(r.WorkflowResources, d.(*WorkflowResource)) +func (r *abstractResource) CanDelete() bool { + return r.IsDraft // only draft bookings can be deleted +} + +func (ao *abstractResource) GetAccessor(request *tools.APIRequest) utils.Accessor { + return nil +} + +func (ao *abstractResource) GetCreatorID() string { + return ao.CreatorID +} + +func (abs *abstractResource) SetResourceModel(model *resource_model.ResourceModel) { + abs.ResourceModel = model +} + +type AbstractResource[T InstanceITF] struct { + abstractResource + Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource +} + +func (abs *AbstractResource[T]) SetAllowedInstances(request *tools.APIRequest) { + abs.Instances = verifyAuthAction[T](abs.Instances, request) +} + +func (abs *AbstractResource[T]) GetPartnership(request *tools.APIRequest) ResourcePartnerITF { + for _, instance := range abs.Instances { + partners, grps := instance.GetPeerGroups() + for i, p := range grps { + if request == nil { + continue + } + if _, ok := p[request.PeerID]; ok { + return partners[i] + } + } + } + return nil +} + +type AbstractCustomizedResource[T InstanceITF] struct { + abstractResource + 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"` + SelectedInstance T `json:"selected_instance,omitempty" bson:"selected_instance,omitempty"` + SelectedPricing string `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"` +} + +func (abs *AbstractCustomizedResource[T]) SetStartUsage(start time.Time) { + if abs.UsageStart == nil { + abs.UsageStart = &start + } +} + +func (abs *AbstractCustomizedResource[T]) SetEndUsage(end time.Time) { + if abs.UsageEnd == nil { + abs.UsageEnd = &end + } +} + +func (abs *AbstractCustomizedResource[T]) IsBuying(request *tools.APIRequest) bool { + return abs.GetPartnership(request).GetPricing(abs.SelectedPricing).IsBuying() +} + +func (abs *AbstractCustomizedResource[T]) GetLocationEnd() *time.Time { + return abs.UsageEnd +} + +func (abs *AbstractCustomizedResource[T]) GetLocationStart() *time.Time { + return abs.UsageStart +} + +func (abs *AbstractCustomizedResource[T]) GetExplicitDurationInS() float64 { + if abs.ExplicitBookingDurationS == 0 { + if abs.UsageEnd == nil || abs.UsageStart == nil { + return time.Duration(1 * time.Hour).Seconds() + } + return abs.UsageEnd.Sub(*abs.UsageStart).Seconds() + } + return abs.ExplicitBookingDurationS +} + +func (abs *AbstractCustomizedResource[T]) GetPricingID() string { + return abs.SelectedPricing +} + +func (r *AbstractCustomizedResource[T]) GetPrice(request *tools.APIRequest) (float64, error) { + if r.UsageStart == nil || r.UsageEnd == nil { + return 0, errors.New("Usage start and end must be set") + } + partner := r.GetPartnership(request) + if partner != nil && partner.GetPricing(r.SelectedPricing) != nil { + return 0, errors.New("Pricing strategy not found") + } + return partner.GetPricing(r.SelectedPricing).GetPrice(1, 0, *r.UsageStart, *r.UsageEnd, request) +} + +func (abs *AbstractCustomizedResource[T]) GetPartnership(request *tools.APIRequest) ResourcePartnerITF { + partners, grps := abs.SelectedInstance.GetPeerGroups() + for i, p := range grps { + if request == nil { + continue + } + if _, ok := p[request.PeerID]; ok { + return partners[i] + } + } + return nil +} + +func verifyAuthAction[T InstanceITF](baseInstance []T, request *tools.APIRequest) []T { + instances := []T{} + for _, instance := range baseInstance { + _, peerGroups := instance.GetPeerGroups() + for _, peers := range peerGroups { + if request == nil { + continue + } + if grps, ok := peers[request.PeerID]; ok || config.GetConfig().Whitelist { + if (ok && slices.Contains(grps, "*")) || (!ok && config.GetConfig().Whitelist) { + instances = append(instances, instance) + } + for _, grp := range grps { + if slices.Contains(request.Groups, grp) { + instances = append(instances, instance) + } } } } } + return instances } -type ItemResource struct { - Data *DataResource `bson:"data,omitempty" json:"data,omitempty"` - Processing *ProcessingResource `bson:"processing,omitempty" json:"processing,omitempty"` - Storage *StorageResource `bson:"storage,omitempty" json:"storage,omitempty"` - Compute *ComputeResource `bson:"compute,omitempty" json:"compute,omitempty"` - Workflow *WorkflowResource `bson:"workflow,omitempty" json:"workflow,omitempty"` +/* +* VerifyPartnerships is a function that verifies the partnerships of the resource +* an instance can only have one partnership available for a peer +* it returns a boolean + */ +func (abs *AbstractResource[T]) VerifyPartnerships() bool { + // a peer can be part of only one partnership by instance + // may we need to define partnership in a different DB + for _, instance := range abs.Instances { + if !instance.VerifyPartnerships() { + return false + } + } + return true } -func (i *ItemResource) GetAbstractRessource() *resource_model.AbstractResource { - - if i.Data != nil { - return &i.Data.AbstractResource +func (d *AbstractResource[T]) Trim() { + if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.CreatorID}}).IsMySelf(); !ok { + // TODO clean up the peer groups that are not in the allowed peers group + for _, instance := range d.Instances { + instance.ClearPeerGroups() + } } - if i.Processing != nil { - return &i.Processing.AbstractResource - } - if i.Storage != nil { - return &i.Storage.AbstractResource - } - if i.Compute != nil { - return &i.Compute.AbstractResource - } - if i.Workflow != nil { - return &i.Workflow.AbstractResource - } - return nil } + +func (abs *AbstractResource[T]) VerifyAuth(request *tools.APIRequest) bool { + return len(verifyAuthAction[T](abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(request) +} + +type ResourceInstance[T ResourcePartnerITF] struct { + UUID string `json:"id,omitempty" bson:"id,omitempty"` + Location geopoint.GeoPoint `json:"location,omitempty" bson:"location,omitempty"` + Country countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"` + // Url string `json:"url,omitempty" bson:"url,omitempty"` + AccessProtocol string `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"` + Partnerships []T `json:"partner_resource,omitempty" bson:"partner_resource,omitempty"` +} + +func (ri *ResourceInstance[T]) GetID() string { + return ri.UUID +} + +func (r *ResourceInstance[T]) VerifyPartnerships() bool { + peersMultiple := map[string]int{} + for _, p := range r.Partnerships { + for k, g := range p.GetPeerGroups() { + for _, v := range g { + if _, ok := peersMultiple[k+"_"+v]; !ok { + peersMultiple[k+"_"+v] = 0 + } + peersMultiple[k+"_"+v]++ + } + } + } + for _, p := range peersMultiple { + if p > 1 { + return false + } + } + return true +} + +func (ri *ResourceInstance[T]) GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) { + groups := []map[string][]string{} + partners := []ResourcePartnerITF{} + for _, p := range ri.Partnerships { + partners = append(partners, p) + groups = append(groups, p.GetPeerGroups()) + } + return partners, groups +} + +func (ri *ResourceInstance[T]) ClearPeerGroups() { + for _, p := range ri.Partnerships { + p.ClearPeerGroups() + } +} + +type ResourcePartnerShip[T pricing.PricingProfileITF] struct { + Namespace string `json:"namespace" bson:"namespace" default:"default-namespace"` + PeerGroups map[string][]string `json:"peer_groups,omitempty" bson:"peer_groups,omitempty"` + PricingProfiles map[string]T `json:"pricing,omitempty" bson:"pricing,omitempty"` +} + +func (rp *ResourcePartnerShip[T]) GetPricing(id string) pricing.PricingProfileITF { + return rp.PricingProfiles[id] +} + +func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string { + return rp.PeerGroups +} + +func (rp *ResourcePartnerShip[T]) ClearPeerGroups() { + rp.PeerGroups = map[string][]string{} +} + +/* +-> when a workflow should book a resource +-> it must be able to book a resource for a particular time +-> the resource should be available for the time +-> we must be able to parameter the resource for the time +-> before bookin' +*/ diff --git a/models/resources/resource_accessor.go b/models/resources/resource_accessor.go index 6c57b1d..6cf9130 100644 --- a/models/resources/resource_accessor.go +++ b/models/resources/resource_accessor.go @@ -1,6 +1,8 @@ package resources import ( + "slices" + "cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/models/resources/resource_model" @@ -14,15 +16,15 @@ type resourceMongoAccessor[T ResourceInterface] struct { } // New creates a new instance of the computeMongoAccessor -func New[T ResourceInterface](t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller, g func() utils.DBObject) *resourceMongoAccessor[T] { +func NewAccessor[T ResourceInterface](t tools.DataType, request *tools.APIRequest, g func() utils.DBObject) *resourceMongoAccessor[T] { + if !slices.Contains([]tools.DataType{tools.COMPUTE_RESOURCE, tools.STORAGE_RESOURCE, tools.PROCESSING_RESOURCE, tools.WORKFLOW_RESOURCE, tools.DATA_RESOURCE}, t) { + return nil + } return &resourceMongoAccessor[T]{ AbstractAccessor: utils.AbstractAccessor{ - ResourceModelAccessor: resource_model.New(), + ResourceModelAccessor: resource_model.NewAccessor(), Logger: logs.CreateLogger(t.String()), // Create a logger with the data type - Caller: caller, - PeerID: peerID, - User: username, // Set the caller - Groups: groups, // Set the caller + Request: request, Type: t, }, generateData: g, @@ -39,12 +41,14 @@ func (dca *resourceMongoAccessor[T]) DeleteOne(id string) (utils.DBObject, int, func (dca *resourceMongoAccessor[T]) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { set.(T).SetResourceModel(nil) - return utils.GenericUpdateOne(set.(T).Trim(), id, dca, dca.generateData()) // TODO CHANGE + set.(T).Trim() + return utils.GenericUpdateOne(set, id, dca, dca.generateData()) } func (dca *resourceMongoAccessor[T]) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { data.(T).SetResourceModel(nil) - return utils.GenericStoreOne(data.(T).Trim(), dca) + data.(T).Trim() + return utils.GenericStoreOne(data, dca) } func (dca *resourceMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { @@ -53,33 +57,36 @@ func (dca *resourceMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObjec func (dca *resourceMongoAccessor[T]) LoadOne(id string) (utils.DBObject, int, error) { return utils.GenericLoadOne[T](id, func(d utils.DBObject) (utils.DBObject, int, error) { - resources, _, err := dca.ResourceModelAccessor.Search(nil, dca.GetType().String()) + resources, _, err := dca.ResourceModelAccessor.Search(nil, dca.GetType().String(), false) if err == nil && len(resources) > 0 { d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel)) } + d.(T).SetAllowedInstances(dca.Request) return d, 200, nil }, dca) } -func (wfa *resourceMongoAccessor[T]) LoadAll() ([]utils.ShallowDBObject, int, error) { - resources, _, err := wfa.ResourceModelAccessor.Search(nil, wfa.GetType().String()) +func (wfa *resourceMongoAccessor[T]) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { + resources, _, err := wfa.ResourceModelAccessor.Search(nil, wfa.GetType().String(), isDraft) return utils.GenericLoadAll[T](func(d utils.DBObject) utils.ShallowDBObject { if err == nil && len(resources) > 0 { d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel)) } + d.(T).SetAllowedInstances(wfa.Request) return d - }, wfa) + }, isDraft, wfa) } -func (wfa *resourceMongoAccessor[T]) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { - resources, _, err := wfa.ResourceModelAccessor.Search(nil, wfa.GetType().String()) +func (wfa *resourceMongoAccessor[T]) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { + resources, _, err := wfa.ResourceModelAccessor.Search(nil, wfa.GetType().String(), false) return utils.GenericSearch[T](filters, search, wfa.getResourceFilter(search), func(d utils.DBObject) utils.ShallowDBObject { if err == nil && len(resources) > 0 { d.(T).SetResourceModel(resources[0].(*resource_model.ResourceModel)) } + d.(T).SetAllowedInstances(wfa.Request) return d - }, wfa) + }, isDraft, wfa) } func (abs *resourceMongoAccessor[T]) getResourceFilter(search string) *dbs.Filters { diff --git a/models/resources/resource_model/resource_model.go b/models/resources/resource_model/resource_model.go index c4aaff4..c2b4006 100644 --- a/models/resources/resource_model/resource_model.go +++ b/models/resources/resource_model/resource_model.go @@ -2,13 +2,9 @@ package resource_model import ( "encoding/json" - "slices" - "cloud.o-forge.io/core/oc-lib/config" - "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/google/uuid" ) type WebResource struct { @@ -16,94 +12,6 @@ type WebResource struct { Path string `bson:"path,omitempty" json:"path,omitempty"` // Path is the path of the URL } -/* -* AbstractResource is a struct that represents a resource -* it defines the resource data - */ -type AbstractResource struct { - utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) - ShortDescription string `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource - Description string `json:"description,omitempty" bson:"description,omitempty"` // Description is the description of the resource - Logo string `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"` // Logo is the logo of the resource - Owner string `json:"owner,omitempty" bson:"owner,omitempty" validate:"required"` // Owner is the owner of the resource - OwnerLogo string `json:"owner_logo,omitempty" bson:"owner_logo,omitempty"` // OwnerLogo is the owner logo of the resource - SourceUrl string `json:"source_url,omitempty" bson:"source_url,omitempty" validate:"required"` // SourceUrl is the source URL of the resource - PeerID string `json:"peer_id,omitempty" bson:"peer_id,omitempty" validate:"required"` // PeerID is the ID of the peer getting this resource - License string `json:"license,omitempty" bson:"license,omitempty"` // License is the license of the resource - ResourceModel *ResourceModel `json:"resource_model,omitempty" bson:"resource_model,omitempty"` // ResourceModel is the model of the resource - - AllowedPeersGroup map[string][]string `json:"allowed_peers_group,omitempty" bson:"allowed_peers_group,omitempty"` // AllowedPeersGroup is the group of allowed peers - - Price string `json:"price,omitempty" bson:"price,omitempty"` // Price is the price of access to the resource - Currency string `json:"currency,omitempty" bson:"currency,omitempty"` // Currency is the currency of the price -} - -func (ao *AbstractResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return nil -} - -func (abs *AbstractResource) SetResourceModel(model *ResourceModel) { - abs.ResourceModel = model -} - -func (abs *AbstractResource) VerifyAuth(username string, peerID string, groups []string) bool { - if grps, ok := abs.AllowedPeersGroup[peerID]; ok || config.GetConfig().Whitelist { - if (ok && slices.Contains(grps, "*")) || (!ok && config.GetConfig().Whitelist) { - return true - } - for _, grp := range grps { - if slices.Contains(groups, grp) { - return true - } - } - } - return abs.AbstractObject.VerifyAuth(username, peerID, groups) -} - -/* -* GetModelType returns the type of the model key - */ -func (abs *AbstractResource) GetModelType(cat string, key string) interface{} { - if abs.ResourceModel == nil || abs.ResourceModel.Model == nil { - return nil - } - if _, ok := abs.ResourceModel.Model[key]; !ok { - return nil - } - return abs.ResourceModel.Model[cat][key].Type -} - -/* -* GetModelKeys returns the keys of the model - */ -func (abs *AbstractResource) GetModelKeys() []string { - keys := make([]string, 0) - for k := range abs.ResourceModel.Model { - keys = append(keys, k) - } - return keys -} - -/* -* GetModelReadOnly returns the readonly of the model key - */ -func (abs *AbstractResource) GetModelReadOnly(cat string, key string) interface{} { - if abs.ResourceModel == nil || abs.ResourceModel.Model == nil { - return nil - } - if _, ok := abs.ResourceModel.Model[key]; !ok { - return nil - } - return abs.ResourceModel.Model[cat][key].ReadOnly -} - -func (d *AbstractResource) Trim() *AbstractResource { - if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.PeerID}}).IsMySelf(); !ok { - d.AllowedPeersGroup = map[string][]string{} - } - return d -} - type Model struct { Type string `json:"type,omitempty" bson:"type,omitempty"` // Type is the type of the model ReadOnly bool `json:"readonly,omitempty" bson:"readonly,omitempty"` // ReadOnly is the readonly of the model @@ -115,38 +23,26 @@ type Model struct { * Warning: This struct is not user available, it is only used by the system */ type ResourceModel struct { - UUID string `json:"id,omitempty" bson:"id,omitempty" validate:"required"` + utils.AbstractObject ResourceType string `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"` VarRefs map[string]string `json:"var_refs,omitempty" bson:"var_refs,omitempty"` // VarRefs is the variable references of the model Model map[string]map[string]Model `json:"model,omitempty" bson:"model,omitempty"` } -func (ao *ResourceModel) GetID() string { - return ao.UUID +func (d *ResourceModel) StoreDraftDefault() { + d.Name = d.ResourceType + " Resource Model" + d.IsDraft = false } -func (ao *ResourceModel) UpToDate(user string, create bool) {} - -func (r *ResourceModel) GenerateID() { - r.UUID = uuid.New().String() -} - -func (d *ResourceModel) GetName() string { - return d.UUID -} - -func (abs *ResourceModel) VerifyAuth(username string, peerID string, groups []string) bool { +func (abs *ResourceModel) VerifyAuth(request *tools.APIRequest) bool { return true } -func (d *ResourceModel) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { +func (d *ResourceModel) GetAccessor(request *tools.APIRequest) utils.Accessor { return &ResourceModelMongoAccessor{ utils.AbstractAccessor{ - Type: tools.RESOURCE_MODEL, - PeerID: peerID, - Groups: groups, - User: username, - Caller: caller, + Type: tools.RESOURCE_MODEL, + Request: request, }, } } diff --git a/models/resources/resource_model/resource_model_mongo_accessor.go b/models/resources/resource_model/resource_model_mongo_accessor.go index a64e9ff..f0186de 100644 --- a/models/resources/resource_model/resource_model_mongo_accessor.go +++ b/models/resources/resource_model/resource_model_mongo_accessor.go @@ -15,7 +15,7 @@ type ResourceModelMongoAccessor struct { * Nothing special here, just the basic CRUD operations */ -func New() *ResourceModelMongoAccessor { +func NewAccessor() *ResourceModelMongoAccessor { return &ResourceModelMongoAccessor{ utils.AbstractAccessor{ Type: tools.RESOURCE_MODEL, @@ -46,17 +46,17 @@ func (a *ResourceModelMongoAccessor) LoadOne(id string) (utils.DBObject, int, er }, a) } -func (a *ResourceModelMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { +func (a *ResourceModelMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { return utils.GenericLoadAll[*ResourceModel](func(d utils.DBObject) utils.ShallowDBObject { return d - }, a) + }, isDraft, a) } -func (a *ResourceModelMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { +func (a *ResourceModelMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { return utils.GenericSearch[*ResourceModel](filters, search, &dbs.Filters{ Or: map[string][]dbs.Filter{ "resource_type": {{Operator: dbs.LIKE.String(), Value: search}}, }, - }, func(d utils.DBObject) utils.ShallowDBObject { return d }, a) + }, func(d utils.DBObject) utils.ShallowDBObject { return d }, isDraft, a) } diff --git a/models/resources/storage.go b/models/resources/storage.go index aa3a019..7ae511d 100644 --- a/models/resources/storage.go +++ b/models/resources/storage.go @@ -1,61 +1,138 @@ package resources import ( - "cloud.o-forge.io/core/oc-lib/models/resources/resource_model" + "errors" + "time" + + "cloud.o-forge.io/core/oc-lib/models/common" + "cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" ) -type StorageSize int - -// StorageType - Enum that defines the type of storage -const ( - GB StorageSize = iota - MB - KB -) - -var argoType = [...]string{ - "Gi", - "Mi", - "Ki", -} - -// New creates a new instance of the StorageResource struct -func (dma StorageSize) ToArgo() string { - return argoType[dma] -} - -// enum of a data type -type StorageType int - -const ( - FILE = iota - STREAM - API - DATABASE - S3 - MEMORY - HARDWARE -) - /* * StorageResource is a struct that represents a storage resource * it defines the resource storage */ type StorageResource struct { - resource_model.AbstractResource // AbstractResource contains the basic fields of an object (id, name) - resource_model.WebResource - Type StorageType `bson:"type,omitempty" json:"type,omitempty"` // Type is the type of the storage - Acronym string `bson:"acronym,omitempty" json:"acronym,omitempty"` // Acronym is the acronym of the storage - SizeType StorageSize `bson:"size_type" json:"size_type" default:"0"` // SizeType is the type of the storage size - Size uint `bson:"size,omitempty" json:"size,omitempty"` // Size is the size of the storage - Local bool `bson:"local" json:"local"` // Local is a flag that indicates if the storage is local - Encryption bool `bson:"encryption,omitempty" json:"encryption,omitempty"` // Encryption is a flag that indicates if the storage is encrypted - Redundancy string `bson:"redundancy,omitempty" json:"redundancy,omitempty"` // Redundancy is the redundancy of the storage - Throughput string `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage + AbstractResource[*StorageResourceInstance] // AbstractResource contains the basic fields of an object (id, name) + Type common.StorageType `bson:"type,omitempty" json:"type,omitempty"` // Type is the type of the storage + Acronym string `bson:"acronym,omitempty" json:"acronym,omitempty"` // Acronym is the acronym of the storage } -func (d *StorageResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New[*StorageResource](tools.STORAGE_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &StorageResource{} }) // Create a new instance of the accessor +func (d *StorageResource) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor[*StorageResource](tools.STORAGE_RESOURCE, request, func() utils.DBObject { return &StorageResource{} }) // Create a new instance of the accessor +} + +type StorageResourceInstance struct { + ResourceInstance[*StorageResourcePartnership] + Local bool `bson:"local" json:"local"` + SecurityLevel string `bson:"security_level,omitempty" json:"security_level,omitempty"` + SizeType common.StorageSize `bson:"size_type" json:"size_type" default:"0"` // SizeType is the type of the storage size + SizeGB uint `bson:"size,omitempty" json:"size,omitempty"` // Size is the size of the storage + Encryption bool `bson:"encryption,omitempty" json:"encryption,omitempty"` // Encryption is a flag that indicates if the storage is encrypted + Redundancy string `bson:"redundancy,omitempty" json:"redundancy,omitempty"` // Redundancy is the redundancy of the storage + Throughput string `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage +} + +func (i *StorageResourceInstance) GetID() string { + return i.UUID +} + +type StorageResourcePartnership struct { + ResourcePartnerShip[*StorageResourcePricingProfile] + MaxSizeGBAllowed float64 `json:"allowed_gb,omitempty" bson:"allowed_gb,omitempty"` + OnlyEncryptedAllowed bool `json:"personal_data_allowed,omitempty" bson:"personal_data_allowed,omitempty"` +} + +type PrivilegeStoragePricingStrategy int + +const ( + BASIC_STORAGE PrivilegeStoragePricingStrategy = iota + GARANTED_ON_DELAY_STORAGE + GARANTED_STORAGE +) + +func (t PrivilegeStoragePricingStrategy) String() string { + return [...]string{"BASIC_STORAGE", "GARANTED_ON_DELAY_STORAGE", "GARANTED_STORAGE"}[t] +} + +type StorageResourcePricingStrategy int + +const ( + PER_DATA_STORED StorageResourcePricingStrategy = iota + PER_TB_STORED + PER_GB_STORED + PER_MB_STORED + PER_KB_STORED +) + +func (t StorageResourcePricingStrategy) GetStrategy() string { + return [...]string{"PER_DATA_STORED", "PER_GB_STORED", "PER_MB_STORED", "PER_KB_STORED"}[t] +} + +func (t StorageResourcePricingStrategy) GetStrategyValue() int { + return int(t) +} + +func ToStorageResourcePricingStrategy(i int) StorageResourcePricingStrategy { + return StorageResourcePricingStrategy(i) +} + +func (t StorageResourcePricingStrategy) GetQuantity(amountOfDataGB float64) (float64, error) { + switch t { + case PER_DATA_STORED: + return amountOfDataGB, nil + case PER_TB_STORED: + return amountOfDataGB * 1000, nil + case PER_GB_STORED: + return amountOfDataGB, nil + case PER_MB_STORED: + return (amountOfDataGB * 1000), nil + case PER_KB_STORED: + return amountOfDataGB * 1000000, nil + } + return 0, errors.New("Pricing strategy not found") +} + +type StorageResourcePricingProfile struct { + Pricing *pricing.PricingStrategy[StorageResourcePricingStrategy] `json:"cpus_prices,omitempty" bson:"cpus_prices,omitempty"` // CPUsPrices is the prices of the CPUs + pricing.ExploitPricingProfile // ExploitPricingProfile is the pricing profile of a storage it means that we exploit the resource for an amount of continuous time +} + +func (p *StorageResourcePricingProfile) IsBuying() bool { + return p.Pricing.BuyingStrategy != pricing.PAY_PER_USE +} + +func (p *StorageResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) { + return p.Pricing.GetPrice(amountOfData, val, start, &end) +} + +type CustomizedStorageResource struct { + AbstractCustomizedResource[*StorageResourceInstance] + StorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"` +} + +func (r *CustomizedStorageResource) GetType() tools.DataType { + return tools.STORAGE_RESOURCE +} + +func (r *CustomizedStorageResource) GetPrice(request *tools.APIRequest) (float64, error) { + if r.UsageStart == nil || r.UsageEnd == nil { + return 0, errors.New("Usage start and end must be set") + } + partner := r.GetPartnership(request) + if partner != nil && partner.GetPricing(r.SelectedPricing) != nil { + return 0, errors.New("Pricing strategy not found") + } + pricing := partner.GetPricing(r.SelectedPricing) + var err error + amountOfData := float64(1) + if pricing.GetOverrideStrategyValue() >= 0 { + amountOfData, err = ToStorageResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.StorageGB) + if err != nil { + return 0, err + } + } + return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, request) } diff --git a/models/resources/workflow.go b/models/resources/workflow.go index f3173be..31d9fb0 100644 --- a/models/resources/workflow.go +++ b/models/resources/workflow.go @@ -1,18 +1,96 @@ package resources import ( - "cloud.o-forge.io/core/oc-lib/models/resources/resource_model" + "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" ) -// WorkflowResource is a struct that represents a workflow resource -// it defines the resource workflow -type WorkflowResource struct { - resource_model.AbstractResource +// COMPLEX SHOULD BE REFACTORED +// we don't have any information about the accessor +type abstractWorkflowResource struct { + ExploitedResourceSet WorkflowID string `bson:"workflow_id,omitempty" json:"workflow_id,omitempty"` // WorkflowID is the ID of the native workflow } -func (d *WorkflowResource) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New[*WorkflowResource](tools.WORKFLOW_RESOURCE, username, peerID, groups, caller, func() utils.DBObject { return &WorkflowResource{} }) // Create a new instance of the accessor +// WorkflowResource is a struct that represents a workflow resource +// it defines the resource workflow +type WorkflowResource struct { + AbstractResource[*ResourceInstance[*ResourcePartnerShip[*WorkflowResourcePricingProfile]]] + abstractWorkflowResource +} + +type CustomizedWorkflowResource struct { + AbstractCustomizedResource[*ResourceInstance[*ResourcePartnerShip[*WorkflowResourcePricingProfile]]] + abstractWorkflowResource +} + +func (r *CustomizedWorkflowResource) GetType() tools.DataType { + return tools.WORKFLOW_RESOURCE +} + +func (d *WorkflowResource) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor[*WorkflowResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} }) // Create a new instance of the accessor +} + +type WorkflowResourcePricingProfile struct { + ExploitedResourceSet + pricing.ExploitPricingProfile // ExploitPricingProfile is the pricing profile of a workflow it means that we exploit the resource for an amount of continuous time +} + +func (p *WorkflowResourcePricingProfile) IsBuying() bool { + return false +} + +/* +* Missing wich Instance is selected +* + */ + +func (p *WorkflowResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) { + // load workflow + price := float64(0) + pp, err := getPrice[*CustomizedDataResource](p.DataResources, amountOfData, val, start, end, request, params...) + if err != nil { + return 0, err + } + price += pp + pp, err = getPrice[*CustomizedStorageResource](p.StorageResources, amountOfData, val, start, end, request, params...) + if err != nil { + return 0, err + } + price += pp + pp, err = getPrice[*CustomizedProcessingResource](p.ProcessingResources, amountOfData, val, start, end, request, params...) + if err != nil { + return 0, err + } + price += pp + pp, err = getPrice[*CustomizedComputeResource](p.ComputeResources, amountOfData, val, start, end, request, params...) + if err != nil { + return 0, err + } + price += pp + pp, err = getPrice[*CustomizedWorkflowResource](p.WorkflowResources, amountOfData, val, start, end, request, params...) + if err != nil { + return 0, err + } + price += pp + return price, nil +} + +func getPrice[T ShallowResourceInterface](arr []T, amountOfData float64, val float64, start time.Time, end time.Time, request *tools.APIRequest, params ...string) (float64, error) { + // load workflow + price := float64(0) + for _, data := range arr { + partner := data.GetPartnership(request) + pricing := partner.GetPricing(data.GetPricingID()) + pp, err := pricing.GetPrice(amountOfData, val, start, end, request) + if err != nil { + return 0, err + } + price += pp + } + return price, nil } diff --git a/models/utils/abstracts.go b/models/utils/abstracts.go index 9a386d5..285fa30 100644 --- a/models/utils/abstracts.go +++ b/models/utils/abstracts.go @@ -2,17 +2,13 @@ package utils import ( "encoding/json" - "errors" - "fmt" "time" "cloud.o-forge.io/core/oc-lib/dbs" - "cloud.o-forge.io/core/oc-lib/dbs/mongo" "cloud.o-forge.io/core/oc-lib/tools" "github.com/go-playground/validator/v10" "github.com/google/uuid" "github.com/rs/zerolog" - mgb "go.mongodb.org/mongo-driver/mongo" ) // single instance of the validator used in every model Struct to validate the fields @@ -33,6 +29,7 @@ const ( type AbstractObject struct { UUID string `json:"id,omitempty" bson:"id,omitempty" validate:"required"` Name string `json:"name,omitempty" bson:"name,omitempty" validate:"required"` + IsDraft bool `json:"is_draft" bson:"is_draft" default:"false"` UpdateDate time.Time `json:"update_date" bson:"update_date"` LastPeerWriter string `json:"last_peer_writer" bson:"last_peer_writer"` CreatorID string `json:"creator_id" bson:"creator_id" default:"unknown"` @@ -45,6 +42,22 @@ func (r *AbstractObject) GenerateID() { } } +func (r *AbstractObject) StoreDraftDefault() { + r.IsDraft = false +} + +func (r *AbstractObject) CanUpdate(set DBObject) (bool, DBObject) { + return true, set +} + +func (r *AbstractObject) CanDelete() bool { + return true +} + +func (r *AbstractObject) IsDrafted() bool { + return r.IsDraft +} + // GetID implements ShallowDBObject. func (ao AbstractObject) GetID() string { return ao.UUID @@ -63,8 +76,8 @@ func (ao *AbstractObject) UpToDate(user string, create bool) { } } -func (ao *AbstractObject) VerifyAuth(username string, peerID string, groups []string) bool { - return ao.AccessMode == Public || ao.CreatorID == username +func (ao *AbstractObject) VerifyAuth(request *tools.APIRequest) bool { + return ao.AccessMode == Public || (request != nil && ao.CreatorID == request.Username) } func (ao *AbstractObject) GetObjectFilters(search string) *dbs.Filters { @@ -96,171 +109,43 @@ func (dma *AbstractObject) Serialize(obj DBObject) map[string]interface{} { type AbstractAccessor struct { Logger zerolog.Logger // Logger is the logger of the accessor, it's a specilized logger for the accessor Type tools.DataType // Type is the data type of the accessor - Caller *tools.HTTPCaller // Caller is the http caller of the accessor (optionnal) only need in a peer connection - PeerID string // PeerID is the id of the peer - Groups []string // Groups is the list of groups that can access the accessor - User string // User is the user that is using the accessor + Request *tools.APIRequest // Caller is the http caller of the accessor (optionnal) only need in a peer connection ResourceModelAccessor Accessor } +func (r *AbstractAccessor) GetRequest() *tools.APIRequest { + return r.Request +} + func (dma *AbstractAccessor) GetUser() string { - return dma.User + if dma.Request == nil { + return "" + } + return dma.Request.Username } func (dma *AbstractAccessor) GetPeerID() string { - return dma.PeerID + if dma.Request == nil { + return "" + } + return dma.Request.PeerID } func (dma *AbstractAccessor) GetGroups() []string { - return dma.Groups + if dma.Request == nil { + return []string{} + } + return dma.Request.Groups } func (dma *AbstractAccessor) GetLogger() *zerolog.Logger { return &dma.Logger } - -func (dma *AbstractAccessor) VerifyAuth() string { - return "" -} - func (dma *AbstractAccessor) GetType() tools.DataType { return dma.Type } func (dma *AbstractAccessor) GetCaller() *tools.HTTPCaller { - return dma.Caller -} - -// GenericLoadOne loads one object from the database (generic) -func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) { - data.GenerateID() - data.UpToDate(a.GetUser(), true) - f := dbs.Filters{ - Or: map[string][]dbs.Filter{ - "abstractresource.abstractobject.name": {{ - Operator: dbs.LIKE.String(), - Value: data.GetName(), - }}, - "abstractobject.name": {{ - Operator: dbs.LIKE.String(), - Value: data.GetName(), - }}, - }, - } - if !data.VerifyAuth(a.GetUser(), a.GetPeerID(), a.GetGroups()) { - return nil, 403, errors.New("You are not allowed to access this collaborative area") - } - if cursor, _, _ := a.Search(&f, ""); len(cursor) > 0 { - return nil, 409, errors.New(a.GetType().String() + " with name " + data.GetName() + " already exists") - } - err := validate.Struct(data) - if err != nil { - return nil, 422, err - } - id, code, err := mongo.MONGOService.StoreOne(data, data.GetID(), a.GetType().String()) - if err != nil { - a.GetLogger().Error().Msg("Could not store " + data.GetName() + " to db. Error: " + err.Error()) - return nil, code, err - } - return a.LoadOne(id) -} - -// GenericLoadOne loads one object from the database (generic) -func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) { - res, code, err := a.LoadOne(id) - if err != nil { - a.GetLogger().Error().Msg("Could not retrieve " + id + " to db. Error: " + err.Error()) - return nil, code, err - } - if !res.VerifyAuth(a.GetUser(), a.GetPeerID(), a.GetGroups()) { - return nil, 403, errors.New("You are not allowed to access this collaborative area") - } - _, code, err = mongo.MONGOService.DeleteOne(id, a.GetType().String()) - if err != nil { - a.GetLogger().Error().Msg("Could not delete " + id + " to db. Error: " + err.Error()) - return nil, code, err - } - return res, 200, nil -} - -// GenericLoadOne loads one object from the database (generic) -// json expected in entry is a flatted object no need to respect the inheritance hierarchy -func GenericUpdateOne(set DBObject, id string, a Accessor, new DBObject) (DBObject, int, error) { - r, c, err := a.LoadOne(id) - if err != nil { - return nil, c, err - } - r.UpToDate(a.GetUser(), false) - if !r.VerifyAuth(a.GetUser(), a.GetPeerID(), a.GetGroups()) { - return nil, 403, errors.New("You are not allowed to access this collaborative area") - } - change := set.Serialize(set) // get the changes - loaded := r.Serialize(r) // get the loaded object - - for k, v := range change { // apply the changes, with a flatten method - loaded[k] = v - } - id, code, err := mongo.MONGOService.UpdateOne(new.Deserialize(loaded, new), id, a.GetType().String()) - if err != nil { - a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error()) - return nil, code, err - } - return a.LoadOne(id) -} - -func GenericLoadOne[T DBObject](id string, f func(DBObject) (DBObject, int, error), a Accessor) (DBObject, int, error) { - var data T - res_mongo, code, err := mongo.MONGOService.LoadOne(id, a.GetType().String()) - if err != nil { - a.GetLogger().Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error()) - return nil, code, err - } - res_mongo.Decode(&data) - if !data.VerifyAuth(a.GetUser(), a.GetPeerID(), a.GetGroups()) { - return nil, 403, errors.New("You are not allowed to access this collaborative area") - } - return f(data) -} - -func genericLoadAll[T DBObject](res *mgb.Cursor, code int, err error, f func(DBObject) ShallowDBObject, a Accessor) ([]ShallowDBObject, int, error) { - objs := []ShallowDBObject{} - var results []T - if err != nil { - a.GetLogger().Error().Msg("Could not retrieve any from db. Error: " + err.Error()) - return nil, code, err - } - if err = res.All(mongo.MngoCtx, &results); err != nil { - return nil, 404, err - } - for _, r := range results { - if !r.VerifyAuth(a.GetUser(), a.GetPeerID(), a.GetGroups()) { - continue - } - objs = append(objs, f(r)) - } - return objs, 200, nil -} - -func GenericLoadAll[T DBObject](f func(DBObject) ShallowDBObject, wfa Accessor) ([]ShallowDBObject, int, error) { - res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType().String()) - fmt.Println("res_mongo", res_mongo) - return genericLoadAll[T](res_mongo, code, err, f, wfa) -} - -func GenericSearch[T DBObject](filters *dbs.Filters, search string, defaultFilters *dbs.Filters, - f func(DBObject) ShallowDBObject, wfa Accessor) ([]ShallowDBObject, int, error) { - if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" { - filters = defaultFilters - } - res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType().String()) - return genericLoadAll[T](res_mongo, code, err, f, wfa) -} - -// GenericLoadOne loads one object from the database (generic) -// json expected in entry is a flatted object no need to respect the inheritance hierarchy -func GenericRawUpdateOne(set DBObject, id string, a Accessor) (DBObject, int, error) { - id, code, err := mongo.MONGOService.UpdateOne(set, id, a.GetType().String()) - if err != nil { - a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error()) - return nil, code, err - } - return a.LoadOne(id) + if dma.Request == nil { + return nil + } + return dma.Request.Caller } diff --git a/models/utils/common.go b/models/utils/common.go index ff98910..5c0409a 100644 --- a/models/utils/common.go +++ b/models/utils/common.go @@ -1,8 +1,160 @@ package utils -/* -type Price struct { - Price float64 `json:"price,omitempty" bson:"price,omitempty"` - Currency string `json:"currency,omitempty" bson:"currency,omitempty"` +import ( + "errors" + "fmt" + + "cloud.o-forge.io/core/oc-lib/dbs" + "cloud.o-forge.io/core/oc-lib/dbs/mongo" + mgb "go.mongodb.org/mongo-driver/mongo" +) + +type Owner struct { + Name string `json:"name,omitempty" bson:"name,omitempty"` + Logo string `json:"logo,omitempty" bson:"logo,omitempty"` +} + +// GenericLoadOne loads one object from the database (generic) +func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) { + data.GenerateID() + data.StoreDraftDefault() + data.UpToDate(a.GetUser(), true) + f := dbs.Filters{ + Or: map[string][]dbs.Filter{ + "abstractresource.abstractobject.name": {{ + Operator: dbs.LIKE.String(), + Value: data.GetName(), + }}, + "abstractobject.name": {{ + Operator: dbs.LIKE.String(), + Value: data.GetName(), + }}, + }, + } + if !data.VerifyAuth(a.GetRequest()) { + return nil, 403, errors.New("you are not allowed to access this collaborative area") + } + if cursor, _, _ := a.Search(&f, "", data.IsDrafted()); len(cursor) > 0 { + return nil, 409, errors.New(a.GetType().String() + " with name " + data.GetName() + " already exists") + } + err := validate.Struct(data) + if err != nil { + return nil, 422, err + } + id, code, err := mongo.MONGOService.StoreOne(data, data.GetID(), a.GetType().String()) + if err != nil { + a.GetLogger().Error().Msg("Could not store " + data.GetName() + " to db. Error: " + err.Error()) + return nil, code, err + } + return a.LoadOne(id) +} + +// GenericLoadOne loads one object from the database (generic) +func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) { + res, code, err := a.LoadOne(id) + if !res.CanDelete() { + return nil, 403, errors.New("you are not allowed to delete this collaborative area") + } + if err != nil { + a.GetLogger().Error().Msg("Could not retrieve " + id + " to db. Error: " + err.Error()) + return nil, code, err + } + if !res.VerifyAuth(a.GetRequest()) { + return nil, 403, errors.New("you are not allowed to access this collaborative area") + } + _, code, err = mongo.MONGOService.DeleteOne(id, a.GetType().String()) + if err != nil { + a.GetLogger().Error().Msg("Could not delete " + id + " to db. Error: " + err.Error()) + return nil, code, err + } + return res, 200, nil +} + +// GenericLoadOne loads one object from the database (generic) +// json expected in entry is a flatted object no need to respect the inheritance hierarchy +func GenericUpdateOne(set DBObject, id string, a Accessor, new DBObject) (DBObject, int, error) { + r, c, err := a.LoadOne(id) + if err != nil { + return nil, c, err + } + ok, newSet := r.CanUpdate(set) + if !ok { + return nil, 403, errors.New("you are not allowed to delete this collaborative area") + } + set = newSet + r.UpToDate(a.GetUser(), false) + if !r.VerifyAuth(a.GetRequest()) { + return nil, 403, errors.New("you are not allowed to access this collaborative area") + } + change := set.Serialize(set) // get the changes + loaded := r.Serialize(r) // get the loaded object + + for k, v := range change { // apply the changes, with a flatten method + loaded[k] = v + } + id, code, err := mongo.MONGOService.UpdateOne(new.Deserialize(loaded, new), id, a.GetType().String()) + if err != nil { + a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error()) + return nil, code, err + } + return a.LoadOne(id) +} + +func GenericLoadOne[T DBObject](id string, f func(DBObject) (DBObject, int, error), a Accessor) (DBObject, int, error) { + var data T + res_mongo, code, err := mongo.MONGOService.LoadOne(id, a.GetType().String()) + if err != nil { + a.GetLogger().Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error()) + return nil, code, err + } + res_mongo.Decode(&data) + if !data.VerifyAuth(a.GetRequest()) { + return nil, 403, errors.New("you are not allowed to access this collaborative area") + } + return f(data) +} + +func genericLoadAll[T DBObject](res *mgb.Cursor, code int, err error, onlyDraft bool, f func(DBObject) ShallowDBObject, a Accessor) ([]ShallowDBObject, int, error) { + objs := []ShallowDBObject{} + var results []T + if err != nil { + a.GetLogger().Error().Msg("Could not retrieve any from db. Error: " + err.Error()) + return nil, code, err + } + if err = res.All(mongo.MngoCtx, &results); err != nil { + return nil, 404, err + } + for _, r := range results { + if !r.VerifyAuth(a.GetRequest()) || f(r) == nil || (onlyDraft && !r.IsDrafted()) || (!onlyDraft && r.IsDrafted()) { + continue + } + objs = append(objs, f(r)) + } + return objs, 200, nil +} + +func GenericLoadAll[T DBObject](f func(DBObject) ShallowDBObject, onlyDraft bool, wfa Accessor) ([]ShallowDBObject, int, error) { + res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType().String()) + fmt.Println("res_mongo", res_mongo) + return genericLoadAll[T](res_mongo, code, err, onlyDraft, f, wfa) +} + +func GenericSearch[T DBObject](filters *dbs.Filters, search string, defaultFilters *dbs.Filters, + f func(DBObject) ShallowDBObject, onlyDraft bool, wfa Accessor) ([]ShallowDBObject, int, error) { + if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" { + filters = defaultFilters + } + res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType().String()) + return genericLoadAll[T](res_mongo, code, err, onlyDraft, f, wfa) +} + +// GenericLoadOne loads one object from the database (generic) +// json expected in entry is a flatted object no need to respect the inheritance hierarchy +func GenericRawUpdateOne(set DBObject, id string, a Accessor) (DBObject, int, error) { + id, code, err := mongo.MONGOService.UpdateOne(set, id, a.GetType().String()) + if err != nil { + a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error()) + return nil, code, err + } + return a.LoadOne(id) } -*/ diff --git a/models/utils/interfaces.go b/models/utils/interfaces.go index c99aa62..b82de6d 100644 --- a/models/utils/interfaces.go +++ b/models/utils/interfaces.go @@ -20,23 +20,28 @@ type DBObject interface { GenerateID() GetID() string GetName() string + IsDrafted() bool + StoreDraftDefault() + CanUpdate(set DBObject) (bool, DBObject) + CanDelete() bool UpToDate(user string, create bool) - VerifyAuth(username string, PeerID string, groups []string) bool + VerifyAuth(request *tools.APIRequest) bool Deserialize(j map[string]interface{}, obj DBObject) DBObject Serialize(obj DBObject) map[string]interface{} - GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) Accessor + GetAccessor(request *tools.APIRequest) Accessor } // Accessor is an interface that defines the basic methods for an Accessor type Accessor interface { + GetRequest() *tools.APIRequest GetType() tools.DataType GetUser() string GetPeerID() string GetGroups() []string GetLogger() *zerolog.Logger GetCaller() *tools.HTTPCaller - Search(filters *dbs.Filters, search string) ([]ShallowDBObject, int, error) - LoadAll() ([]ShallowDBObject, int, error) + Search(filters *dbs.Filters, search string, isDraft bool) ([]ShallowDBObject, int, error) + LoadAll(isDraft bool) ([]ShallowDBObject, int, error) LoadOne(id string) (DBObject, int, error) DeleteOne(id string) (DBObject, int, error) CopyOne(data DBObject) (DBObject, int, error) diff --git a/models/workflow/graph/graph.go b/models/workflow/graph/graph.go index 5a5ca3b..9419480 100644 --- a/models/workflow/graph/graph.go +++ b/models/workflow/graph/graph.go @@ -1,8 +1,10 @@ package graph 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/utils" "cloud.o-forge.io/core/oc-lib/tools" ) @@ -13,7 +15,84 @@ type Graph struct { Links []GraphLink `bson:"links" json:"links" default:"{}" validate:"required"` // Links is the list of links between elements in the graph } -func (g *Graph) GetResource(id string) (string, utils.DBObject) { +func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.CustomizedProcessingResource, resource resources.ShallowResourceInterface, f func(GraphItem) resources.ShallowResourceInterface) (float64, float64) { + nearestStart := float64(10000000000) + oneIsInfinite := false + longestDuration := float64(0) + for _, link := range g.Links { + for _, processing := range processings { + var source string // source is the source of the link + if link.Destination.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the destination is the processing and the source is not a compute + source = link.Source.ID + } 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 + } + if source != "" { + if processing.UsageStart != nil { + near := float64(processing.UsageStart.Sub(start).Seconds()) + if near < nearestStart { + nearestStart = near + } + + } + if processing.UsageEnd != nil { + duration := float64(processing.UsageEnd.Sub(*processing.UsageStart).Seconds()) + if longestDuration < duration { + longestDuration = duration + } + } else { + oneIsInfinite = true + } + } + } + } + if oneIsInfinite { + return nearestStart, -1 + } + return nearestStart, longestDuration +} + +func (g *Graph) SetItemStartUsage(graphItemID string, start time.Time) { + g.Items[graphItemID].SetItemStartUsage(start) +} + +func (g *Graph) SetItemEndUsage(graphItemID string, end time.Time) { + g.Items[graphItemID].SetItemEndUsage(end) +} + +/* +* GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing + */ +func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string) float64 { + currents := []float64{} // list of current time + for _, link := range g.Links { // for each link + var source string // source is the source of the link + if link.Destination.ID == processingID && g.Items[link.Source.ID].Processing == nil { // if the destination is the processing and the source is not a compute + source = link.Source.ID + } else if link.Source.ID == processingID && g.Items[link.Source.ID].Processing == nil { // if the source is the processing and the destination is not a compute + source = link.Destination.ID + } + if source == "" { // if source is empty, continue + continue + } + _, item := g.GetResource(source) // get the resource of the source + current := item.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 + } + current += g.GetAverageTimeProcessingBeforeStart(current, source) // 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 { + if current > max { + max = current + } + } + return max +} + +func (g *Graph) GetResource(id string) (string, resources.ShallowResourceInterface) { if item, ok := g.Items[id]; ok { if item.Data != nil { return tools.DATA_RESOURCE.String(), item.Data @@ -32,11 +111,41 @@ func (g *Graph) GetResource(id string) (string, utils.DBObject) { // GraphItem is a struct that represents an item in a graph type GraphItem struct { - ID string `bson:"id" json:"id" validate:"required"` // ID is the unique identifier of the item - Width float64 `bson:"width" json:"width" validate:"required"` // Width is the graphical width of the item - Height float64 `bson:"height" json:"height" validate:"required"` // Height is the graphical height of the item - Position Position `bson:"position" json:"position" validate:"required"` // Position is the graphical position of the item - *resources.ItemResource // ItemResource is the resource of the item affected to the item + ID string `bson:"id" json:"id" validate:"required"` // ID is the unique identifier of the item + Width float64 `bson:"width" json:"width" validate:"required"` // Width is the graphical width of the item + Height float64 `bson:"height" json:"height" validate:"required"` // Height is the graphical height of the item + Position Position `bson:"position" json:"position" validate:"required"` // Position is the graphical position of the item + *resources.ItemExploitedResource // ItemResource is the resource of the item affected to the item +} + +func (g *GraphItem) GetResource() resources.ShallowResourceInterface { + if g.Data != nil { + return g.Data + } else if g.Compute != nil { + return g.Compute + } else if g.Workflow != nil { + return g.Workflow + } else if g.Processing != nil { + return g.Processing + } else if g.Storage != nil { + return g.Storage + } + return nil +} + +func (g *GraphItem) GetPricedItem() pricing.PricedItemITF { + if g.Data != nil { + return g.Data + } else if g.Compute != nil { + return g.Compute + } else if g.Workflow != nil { + return g.Workflow + } else if g.Processing != nil { + return g.Processing + } else if g.Storage != nil { + return g.Storage + } + return nil } // GraphLink is a struct that represents a link between two items in a graph @@ -46,6 +155,20 @@ type GraphLink struct { Style *GraphLinkStyle `bson:"style,omitempty" json:"style,omitempty"` // Style is the graphical style of the link } +// tool function to check if a link is a link between a compute and a resource +func (l *GraphLink) IsComputeLink(g Graph) (bool, string) { + if g.Items == nil { + return false, "" + } + if d, ok := g.Items[l.Source.ID]; ok && d.Compute != nil { + return true, d.Compute.UUID + } + if d, ok := g.Items[l.Destination.ID]; ok && d.Compute != nil { + return true, d.Compute.UUID + } + return false, "" +} + // GraphLinkStyle is a struct that represents the style of a link in a graph type GraphLinkStyle struct { Color int64 `bson:"color" json:"color"` // Color is the graphical color of the link (int description of a color, can be transpose as hex) @@ -64,7 +187,7 @@ type GraphLinkStyle struct { // Position is a struct that represents a graphical position type Position struct { - ID string `json:"id" bson:"id"` // ID reprents ItemID (optionnal), TODO: rename to ItemID + ID string `json:"id" bson:"id"` // ID reprents ItemID (optionnal) X float64 `json:"x" bson:"x" validate:"required"` // X is the graphical x position Y float64 `json:"y" bson:"y" validate:"required"` // Y is the graphical y position } diff --git a/models/workflow/workflow.go b/models/workflow/workflow.go index ddd6cdb..23fb62b 100644 --- a/models/workflow/workflow.go +++ b/models/workflow/workflow.go @@ -2,8 +2,10 @@ package workflow import ( "errors" + "time" "cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area" + "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/resources" "cloud.o-forge.io/core/oc-lib/models/utils" @@ -19,29 +21,61 @@ import ( */ type AbstractWorkflow struct { resources.ResourceSet - Graph *graph.Graph `bson:"graph,omitempty" json:"graph,omitempty"` // Graph UI & logic representation of the workflow - ScheduleActive bool `json:"schedule_active" bson:"schedule_active"` // ScheduleActive is a flag that indicates if the schedule is active, if not the workflow is not scheduled and no execution or booking will be set - Schedule *WorkflowSchedule `bson:"schedule,omitempty" json:"schedule,omitempty"` // Schedule is the schedule of the workflow - Shared []string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workflow + Graph *graph.Graph `bson:"graph,omitempty" json:"graph,omitempty"` // Graph UI & logic representation of the workflow + ScheduleActive bool `json:"schedule_active" bson:"schedule_active"` // ScheduleActive is a flag that indicates if the schedule is active, if not the workflow is not scheduled and no execution or booking will be set + // Schedule *WorkflowSchedule `bson:"schedule,omitempty" json:"schedule,omitempty"` // Schedule is the schedule of the workflow + Shared []string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workflow } -func (w *AbstractWorkflow) GetWorkflows() (list_computings []graph.GraphItem) { +func (d *Workflow) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor(request) // Create a new instance of the accessor +} + +func (w *AbstractWorkflow) GetGraphItems(f func(item graph.GraphItem) bool) (list_datas []graph.GraphItem) { for _, item := range w.Graph.Items { - if item.Workflow != nil { - list_computings = append(list_computings, item) + if f(item) { + list_datas = append(list_datas, item) } } return } -func (w *AbstractWorkflow) GetComputeByRelatedProcessing(processingID string) []*resources.ComputeResource { - storages := []*resources.ComputeResource{} +func (w *AbstractWorkflow) GetResources(f func(item graph.GraphItem) bool) map[string]resources.ShallowResourceInterface { + list_datas := map[string]resources.ShallowResourceInterface{} + for _, item := range w.Graph.Items { + if f(item) { + res := item.GetResource() + list_datas[res.GetID()] = res + } + } + return list_datas +} + +func (w *AbstractWorkflow) GetPricedItem(f func(item graph.GraphItem) bool) map[string]pricing.PricedItemITF { + list_datas := map[string]pricing.PricedItemITF{} + for _, item := range w.Graph.Items { + if f(item) { + res := item.GetResource() + ord := item.GetPricedItem() + list_datas[res.GetID()] = ord + } + } + return list_datas +} + +func (w *AbstractWorkflow) GetByRelatedProcessing(processingID string, g func(item graph.GraphItem) bool) []resources.ShallowResourceInterface { + storages := []resources.ShallowResourceInterface{} for _, link := range w.Graph.Links { - nodeID := link.Destination.ID // we considers that the processing is the destination - node := w.Graph.Items[link.Source.ID].Compute // we are looking for the storage as source - if node == nil { // if the source is not a storage, we consider that the destination is the storage - nodeID = link.Source.ID // and the processing is the source - node = w.Graph.Items[link.Destination.ID].Compute // we are looking for the storage as destination + nodeID := link.Destination.ID + var node resources.ShallowResourceInterface + if g(w.Graph.Items[link.Source.ID]) { + item := w.Graph.Items[link.Source.ID] + node = item.GetResource() + } + if node == nil && g(w.Graph.Items[link.Destination.ID]) { // if the source is not a storage, we consider that the destination is the storage + nodeID = link.Source.ID + item := w.Graph.Items[link.Destination.ID] // and the processing is the source + node = item.GetResource() // we are looking for the storage as destination } if processingID == nodeID && node != nil { // if the storage is linked to the processing storages = append(storages, node) @@ -50,43 +84,24 @@ func (w *AbstractWorkflow) GetComputeByRelatedProcessing(processingID string) [] return storages } -func (w *AbstractWorkflow) GetStoragesByRelatedProcessing(processingID string) []*resources.StorageResource { - storages := []*resources.StorageResource{} - for _, link := range w.Graph.Links { - nodeID := link.Destination.ID // we considers that the processing is the destination - node := w.Graph.Items[link.Source.ID].Storage // we are looking for the storage as source - if node == nil { // if the source is not a storage, we consider that the destination is the storage - nodeID = link.Source.ID // and the processing is the source - node = w.Graph.Items[link.Destination.ID].Storage // we are looking for the storage as destination - } - if processingID == nodeID && node != nil { // if the storage is linked to the processing - storages = append(storages, node) - } - } - return storages +func (wf *AbstractWorkflow) IsProcessing(item graph.GraphItem) bool { + return item.Processing != nil } -func (w *AbstractWorkflow) GetProcessings() (list_computings []graph.GraphItem) { - for _, item := range w.Graph.Items { - if item.Processing != nil { - list_computings = append(list_computings, item) - } - } - return +func (wf *AbstractWorkflow) IsCompute(item graph.GraphItem) bool { + return item.Compute != nil } -// tool function to check if a link is a link between a compute and a resource -func (w *AbstractWorkflow) isDCLink(link graph.GraphLink) (bool, string) { - if w.Graph == nil || w.Graph.Items == nil { - return false, "" - } - if d, ok := w.Graph.Items[link.Source.ID]; ok && d.Compute != nil { - return true, d.Compute.UUID - } - if d, ok := w.Graph.Items[link.Destination.ID]; ok && d.Compute != nil { - return true, d.Compute.UUID - } - return false, "" +func (wf *AbstractWorkflow) IsData(item graph.GraphItem) bool { + return item.Data != nil +} + +func (wf *AbstractWorkflow) IsStorage(item graph.GraphItem) bool { + return item.Storage != nil +} + +func (wf *AbstractWorkflow) IsWorkflow(item graph.GraphItem) bool { + return item.Workflow != nil } /* @@ -98,18 +113,51 @@ type Workflow struct { AbstractWorkflow // AbstractWorkflow contains the basic fields of a workflow } -func (ao *Workflow) VerifyAuth(username string, peerID string, groups []string) bool { +func (w *Workflow) GetNearestStart(start time.Time) float64 { + near := float64(10000000000) + for _, item := range w.Graph.Items { + if item.GetResource().GetLocationStart() == nil { + continue + } + newS := item.GetResource().GetLocationStart() + if newS.Sub(start).Seconds() < near { + near = newS.Sub(start).Seconds() + } + // get the nearest start from start var + } + return near +} + +func (w *Workflow) GetLongestTime(end *time.Time) float64 { + if end == nil { + return -1 + } + longestTime := float64(0) + for _, item := range w.GetGraphItems(w.IsProcessing) { + if item.GetResource().GetLocationEnd() == nil { + continue + } + newS := item.GetResource().GetLocationEnd() + if longestTime < newS.Sub(*end).Seconds() { + longestTime = newS.Sub(*end).Seconds() + } + // get the nearest start from start var + } + return longestTime +} + +func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool { isAuthorized := false if len(ao.Shared) > 0 { for _, shared := range ao.Shared { - shared, code, _ := shallow_collaborative_area.New(tools.COLLABORATIVE_AREA, username, peerID, groups, nil).LoadOne(shared) + shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(shared) if code != 200 || shared == nil { isAuthorized = false } - isAuthorized = shared.VerifyAuth(username, peerID, groups) + isAuthorized = shared.VerifyAuth(request) } } - return ao.AbstractObject.VerifyAuth(username, peerID, groups) || isAuthorized + return ao.AbstractObject.VerifyAuth(request) || isAuthorized } /* @@ -120,19 +168,19 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) { if wfa.Graph == nil { // no graph no booking return false, nil } - accessor := (&resources.ComputeResource{}).GetAccessor("", "", []string{}, caller) + accessor := (&resources.ComputeResource{}).GetAccessor(&tools.APIRequest{Caller: caller}) for _, link := range wfa.Graph.Links { - if ok, dc_id := wfa.isDCLink(link); ok { // check if the link is a link between a compute and a resource - dc, code, _ := accessor.LoadOne(dc_id) + if ok, compute_id := link.IsComputeLink(*wfa.Graph); ok { // check if the link is a link between a compute and a resource + compute, code, _ := accessor.LoadOne(compute_id) if code != 200 { continue } // CHECK BOOKING ON PEER, compute could be a remote one - peerID := dc.(*resources.ComputeResource).PeerID + peerID := compute.(*resources.ComputeResource).CreatorID if peerID == "" { return false, errors.New("no peer id") } // no peer id no booking, we need to know where to book - _, err := (&peer.Peer{}).LaunchPeerExecution(peerID, dc_id, tools.BOOKING, tools.GET, nil, caller) + _, err := (&peer.Peer{}).LaunchPeerExecution(peerID, compute_id, tools.BOOKING, tools.GET, nil, caller) if err != nil { return false, err } @@ -140,7 +188,3 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) { } return true, nil } - -func (d *Workflow) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New(tools.WORKFLOW, username, peerID, groups, caller) // Create a new instance of the accessor -} diff --git a/models/workflow/workflow_history_mongo_accessor.go b/models/workflow/workflow_history_mongo_accessor.go index 13f197a..b5808cc 100644 --- a/models/workflow/workflow_history_mongo_accessor.go +++ b/models/workflow/workflow_history_mongo_accessor.go @@ -8,8 +8,8 @@ import ( type WorkflowHistory struct{ Workflow } -func (d *WorkflowHistory) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New(tools.WORKSPACE_HISTORY, username, peerID, groups, caller) // Create a new instance of the accessor +func (d *WorkflowHistory) GetAccessor(request tools.APIRequest) utils.Accessor { + return NewAccessorHistory(request) // Create a new instance of the accessor } func (r *WorkflowHistory) GenerateID() { r.UUID = uuid.New().String() diff --git a/models/workflow/workflow_mongo_accessor.go b/models/workflow/workflow_mongo_accessor.go index 5f68f8e..428808f 100644 --- a/models/workflow/workflow_mongo_accessor.go +++ b/models/workflow/workflow_mongo_accessor.go @@ -1,183 +1,55 @@ package workflow import ( - "errors" - "fmt" - "slices" - "strings" - "time" - "cloud.o-forge.io/core/oc-lib/dbs" - "cloud.o-forge.io/core/oc-lib/dbs/mongo" "cloud.o-forge.io/core/oc-lib/logs" "cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area" "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/utils" - "cloud.o-forge.io/core/oc-lib/models/workflow_execution" "cloud.o-forge.io/core/oc-lib/models/workspace" "cloud.o-forge.io/core/oc-lib/tools" - cron "github.com/robfig/cron" ) type workflowMongoAccessor struct { - utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) - + utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) computeResourceAccessor utils.Accessor collaborativeAreaAccessor utils.Accessor - executionAccessor utils.Accessor workspaceAccessor utils.Accessor } +func NewAccessorHistory(request *tools.APIRequest) *workflowMongoAccessor { + return new(tools.WORKFLOW_HISTORY, request) +} + +func NewAccessor(request *tools.APIRequest) *workflowMongoAccessor { + return new(tools.WORKFLOW, request) +} + // New creates a new instance of the workflowMongoAccessor -func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *workflowMongoAccessor { +func new(t tools.DataType, request *tools.APIRequest) *workflowMongoAccessor { return &workflowMongoAccessor{ - computeResourceAccessor: (&resources.ComputeResource{}).GetAccessor(username, peerID, groups, nil), - collaborativeAreaAccessor: (&shallow_collaborative_area.ShallowCollaborativeArea{}).GetAccessor(username, peerID, groups, nil), - executionAccessor: (&workflow_execution.WorkflowExecution{}).GetAccessor(username, peerID, groups, nil), - workspaceAccessor: (&workspace.Workspace{}).GetAccessor(username, peerID, groups, nil), + computeResourceAccessor: (&resources.ComputeResource{}).GetAccessor(request), + collaborativeAreaAccessor: (&shallow_collaborative_area.ShallowCollaborativeArea{}).GetAccessor(request), + workspaceAccessor: (&workspace.Workspace{}).GetAccessor(request), AbstractAccessor: utils.AbstractAccessor{ - Logger: logs.CreateLogger(t.String()), // Create a logger with the data type - Caller: caller, - PeerID: peerID, - User: username, - Groups: groups, // Set the caller - Type: t, + Logger: logs.CreateLogger(t.String()), // Create a logger with the data type + Request: request, + Type: t, }, } } -/* -* THERE IS A LOT IN THIS FILE SHOULD BE AWARE OF THE COMMENTS - */ - -/* -* getExecutions is a function that returns the executions of a workflow -* it returns an array of workflow_execution.WorkflowExecution - */ -func (a *workflowMongoAccessor) getExecutions(id string, data *Workflow) ([]*workflow_execution.WorkflowExecution, error) { - workflows_execution := []*workflow_execution.WorkflowExecution{} - if data.Schedule != nil { // only set execution on a scheduled workflow - if data.Schedule.Start == nil { // if no start date, return an error - return workflows_execution, errors.New("should get a start date on the scheduler.") - } - if data.Schedule.End != nil && data.Schedule.End.IsZero() { // if end date is zero, set it to nil - data.Schedule.End = nil - } - if len(data.Schedule.Cron) > 0 { // if cron is set then end date should be set - if data.Schedule.End == nil { - return workflows_execution, errors.New("a cron task should have an end date.") - } - cronStr := strings.Split(data.Schedule.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 nil, errors.New("Bad cron message: " + data.Schedule.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 workflows_execution, errors.New("Bad cron message: " + err.Error()) - } - // loop through the cron schedule to set the executions - for s := sched.Next(*data.Schedule.Start); !s.IsZero() && s.Before(*data.Schedule.End); s = sched.Next(s) { - obj := &workflow_execution.WorkflowExecution{ - AbstractObject: utils.AbstractObject{ - Name: data.Schedule.Name, // set the name of the execution - }, - ExecDate: &s, // set the execution date - EndDate: data.Schedule.End, // set the end date - State: 1, // set the state to 1 (scheduled) - WorkflowID: id, // set the workflow id dependancy of the execution - } - workflows_execution = append(workflows_execution, obj) // append the execution to the array - } - - } else { // if no cron, set the execution to the start date - obj := &workflow_execution.WorkflowExecution{ // create a new execution - AbstractObject: utils.AbstractObject{ - Name: data.Schedule.Name, - }, - ExecDate: data.Schedule.Start, - EndDate: data.Schedule.End, - State: 1, - WorkflowID: id, - } - workflows_execution = append(workflows_execution, obj) // append the execution to the array - } - } - return workflows_execution, nil -} - // DeleteOne deletes a workflow from the database, delete depending executions and bookings func (a *workflowMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { - a.execution(id, &Workflow{ - AbstractWorkflow: AbstractWorkflow{ScheduleActive: false}, - }, true) // delete the executions res, code, err := utils.GenericDeleteOne(id, a) if res != nil && code == 200 { a.execute(res.(*Workflow), true, false) // up to date the workspace for the workflow - a.share(res.(*Workflow), true, a.Caller) + a.share(res.(*Workflow), true, a.GetCaller()) } return res, code, err } -/* -* book is a function that books a workflow on the peers -* it takes the workflow id, the real data and the executions -* it returns an error if the booking fails - */ -func (a *workflowMongoAccessor) book(id string, realData *Workflow, execs []*workflow_execution.WorkflowExecution) error { - if a.Caller == nil || a.Caller.URLS == nil || a.Caller.URLS[tools.BOOKING] == nil { - return errors.New("no caller defined") - } - methods := a.Caller.URLS[tools.BOOKING] - if _, ok := methods[tools.POST]; !ok { - return errors.New("no path found") - } - res, code, _ := a.LoadOne(id) - if code != 200 { - return errors.New("could not load workflow") - } - r := res.(*Workflow) - g := r.Graph - if realData.Graph != nil { // if the graph is set, set it to the real data - g = realData.Graph - } - if g != nil && g.Links != nil && len(g.Links) > 0 { // if the graph is set and has links then book the workflow (even on ourselves) - isDCFound := []string{} - for _, link := range g.Links { - if ok, dc_id := realData.isDCLink(link); ok { // check if the link is a link between a compute and a resource booking is only on compute - if slices.Contains(isDCFound, dc_id) { - continue - } // if the compute is already found, skip it - isDCFound = append(isDCFound, dc_id) - dc, code, _ := a.computeResourceAccessor.LoadOne(dc_id) - if code != 200 { - continue - } - // CHECK BOOKING - peerID := dc.(*resources.ComputeResource).PeerID - if peerID == "" { // no peer id no booking - continue - } - // BOOKING ON PEER - _, err := (&peer.Peer{}).LaunchPeerExecution(peerID, "", tools.BOOKING, tools.POST, - (&workflow_execution.WorkflowExecutions{ // it's the standard model for booking see OC-PEER - WorkflowID: id, // set the workflow id "WHO" - ResourceID: dc_id, // set the compute id "WHERE" - Executions: execs, // set the executions to book "WHAT" - }).Serialize(), a.Caller) - if err != nil { - fmt.Println("BOOKING", err) - return err - } - } - } - } - return nil -} - /* * share is a function that shares a workflow to the peers if the workflow is shared */ @@ -197,10 +69,12 @@ func (a *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *t if ok, _ := paccess.IsMySelf(); 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 + if delete { // if the workflow is deleted, share the deletion orderResourceAccessor utils.Accessor + history := NewHistory() history.StoreOne(history.MapFromWorkflow(res.(*Workflow))) - _, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.DELETE, map[string]interface{}{}, caller) + _, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.DELETE, + map[string]interface{}{}, caller) } else { // if the workflow is updated, share the update _, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.PUT, res.Serialize(res), caller) @@ -212,99 +86,30 @@ func (a *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *t } } -/* -* execution is a create or delete function for the workflow executions depending on the schedule of the workflow - */ -func (a *workflowMongoAccessor) execution(id string, realData *Workflow, delete bool) (int, error) { - nats := tools.NewNATSCaller() // create a new nats caller because executions are sent to the nats for daemons - mongo.MONGOService.DeleteMultiple(map[string]interface{}{ - "state": 1, // only delete the scheduled executions only scheduled if executions are in progress or ended, they should not be deleted for registration - "workflow_id": id, - }, tools.WORKFLOW_EXECUTION.String()) - err := a.book(id, realData, []*workflow_execution.WorkflowExecution{}) // delete the booking of the workflow on the peers - fmt.Println("DELETE BOOKING", err) - nats.SetNATSPub(tools.WORKFLOW.String(), tools.REMOVE, realData) // send the deletion to the nats - if err != nil { - return 409, err - } - - execs, err := a.getExecutions(id, realData) // get the executions of the workflow - if err != nil { - return 422, err - } - if !realData.ScheduleActive || delete { // if the schedule is not active, delete the executions - execs = []*workflow_execution.WorkflowExecution{} - } - err = a.book(id, realData, execs) // book the workflow on the peers - fmt.Println("BOOKING", err) - if err != nil { - return 409, err // if the booking fails, return an error for integrity between peers - } - fmt.Println("BOOKING", delete) - for _, obj := range execs { - _, code, err := a.executionAccessor.StoreOne(obj) - fmt.Println("EXEC", code, err) - if code != 200 { - return code, err - } - } - nats.SetNATSPub(tools.WORKFLOW.String(), tools.CREATE, realData) // send the creation to the nats - return 200, nil -} - // UpdateOne updates a workflow in the database func (a *workflowMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { - res, code, err := a.LoadOne(id) - if code != 200 { - return nil, 409, err - } - // avoid the update if the schedule is the same - avoid := set.(*Workflow).Schedule == nil || (res.(*Workflow).Schedule != nil && res.(*Workflow).ScheduleActive == set.(*Workflow).ScheduleActive && res.(*Workflow).Schedule.Start == set.(*Workflow).Schedule.Start && res.(*Workflow).Schedule.End == set.(*Workflow).Schedule.End && res.(*Workflow).Schedule.Cron == set.(*Workflow).Schedule.Cron) - res, code, err = utils.GenericUpdateOne(set, id, a, &Workflow{}) + res, code, err := utils.GenericUpdateOne(set, id, a, &Workflow{}) if code != 200 { return nil, code, err } workflow := res.(*Workflow) - if !avoid { // if the schedule is not avoided, update the executions - if code, err := a.execution(id, workflow, false); code != 200 { - return nil, code, errors.New("could not update the executions : " + err.Error()) - } - } - fmt.Println("UPDATE", workflow.ScheduleActive, workflow.Schedule) - if workflow.ScheduleActive && workflow.Schedule != nil { // if the workflow is scheduled, update the executions - now := time.Now().UTC() - if (workflow.Schedule.End != nil && now.After(*workflow.Schedule.End)) || (workflow.Schedule.End == nil && workflow.Schedule.Start != nil && now.After(*workflow.Schedule.Start)) { // if the start date is passed, then you can book - workflow.ScheduleActive = false - utils.GenericRawUpdateOne(workflow, id, a) - } // if the start date is passed, update the executions - } - a.execute(workflow, false, false) // update the workspace for the workflow - a.share(workflow, false, a.Caller) // share the update to the peers + a.execute(workflow, false, false) // update the workspace for the workflow + a.share(workflow, false, a.GetCaller()) // share the update to the peers return res, code, nil } // StoreOne stores a workflow in the database func (a *workflowMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { d := data.(*Workflow) - if d.ScheduleActive && d.Schedule != nil { // if the workflow is scheduled, update the executions - now := time.Now().UTC() - if (d.Schedule.End != nil && now.After(*d.Schedule.End)) || (d.Schedule.End == nil && d.Schedule.Start != nil && now.After(*d.Schedule.Start)) { // if the start date is passed, then you can book - d.ScheduleActive = false - } // if the start date is passed, update the executions - } res, code, err := utils.GenericStoreOne(d, a) if err != nil || code != 200 { return nil, code, err } workflow := res.(*Workflow) - a.share(workflow, false, a.Caller) // share the creation to the peers - //store the executions - if code, err := a.execution(res.GetID(), workflow, false); err != nil { - return nil, code, err - } - a.execute(workflow, false, false) // store the workspace for the workflow + a.share(workflow, false, a.GetCaller()) // share the creation to the peers + a.execute(workflow, false, false) // store the workspace for the workflow return res, code, nil } @@ -322,7 +127,7 @@ func (a *workflowMongoAccessor) execute(workflow *Workflow, delete bool, active "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: workflow.Name + "_workspace"}}, }, } - resource, _, err := a.workspaceAccessor.Search(filters, "") + resource, _, err := a.workspaceAccessor.Search(filters, "", workflow.IsDraft) if delete { // if delete is set to true, delete the workspace for _, r := range resource { a.workspaceAccessor.DeleteOne(r.GetID()) @@ -358,22 +163,15 @@ func (a *workflowMongoAccessor) execute(workflow *Workflow, delete bool, active func (a *workflowMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { return utils.GenericLoadOne[*Workflow](id, func(d utils.DBObject) (utils.DBObject, int, error) { w := d.(*Workflow) - if w.ScheduleActive && w.Schedule != nil { // if the workflow is scheduled, update the executions - now := time.Now().UTC() - if (w.Schedule.End != nil && now.After(*w.Schedule.End)) || (w.Schedule.End == nil && w.Schedule.Start != nil && now.After(*w.Schedule.Start)) { // if the start date is passed, then you can book - w.ScheduleActive = false - utils.GenericRawUpdateOne(d, id, a) - } // if the start date is passed, update the executions - } a.execute(w, false, true) // if no workspace is attached to the workflow, create it return d, 200, nil }, a) } -func (a *workflowMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { - return utils.GenericLoadAll[*Workflow](func(d utils.DBObject) utils.ShallowDBObject { return &d.(*Workflow).AbstractObject }, a) +func (a *workflowMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericLoadAll[*Workflow](func(d utils.DBObject) utils.ShallowDBObject { return &d.(*Workflow).AbstractObject }, isDraft, a) } -func (a *workflowMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { - return utils.GenericSearch[*Workflow](filters, search, (&Workflow{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { return d }, a) +func (a *workflowMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericSearch[*Workflow](filters, search, (&Workflow{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { return d }, isDraft, a) } diff --git a/models/workflow/workflow_schedule.go b/models/workflow/workflow_schedule.go deleted file mode 100644 index 14ef12e..0000000 --- a/models/workflow/workflow_schedule.go +++ /dev/null @@ -1,23 +0,0 @@ -package workflow - -import "time" - -// WorkflowSchedule is a struct that contains the scheduling information of a workflow -type ScheduleMode int - -const ( - TASK ScheduleMode = iota - SERVICE -) - -/* -* 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 - */ -type WorkflowSchedule struct { - Mode int64 `json:"mode" bson:"mode" validate:"required"` // Mode is the mode of the schedule (Task or Service) - Name string `json:"name" bson:"name" validate:"required"` // Name is the name of the schedule - Start *time.Time `json:"start" bson:"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" bson:"end,omitempty"` // End is the end time of the schedule - Cron string `json:"cron,omitempty" bson:"cron,omitempty"` // here the cron format : ss mm hh dd MM dw task -} diff --git a/models/workflow/workflow_test.go b/models/workflow/workflow_test.go index 366edf7..dc515e2 100644 --- a/models/workflow/workflow_test.go +++ b/models/workflow/workflow_test.go @@ -13,7 +13,7 @@ func TestStoreOneWorkflow(t *testing.T) { AbstractObject: utils.AbstractObject{Name: "testWorkflow"}, } - wma := New(tools.WORKFLOW, "", "", nil, nil) + wma := NewAccessor(tools.APIRequest{}) id, _, _ := wma.StoreOne(&w) assert.NotEmpty(t, id) @@ -24,7 +24,7 @@ func TestLoadOneWorkflow(t *testing.T) { AbstractObject: utils.AbstractObject{Name: "testWorkflow"}, } - wma := New(tools.WORKFLOW, "", "", nil, nil) + wma := NewAccessor(tools.APIRequest{}) new_w, _, _ := wma.StoreOne(&w) assert.Equal(t, w, new_w) } diff --git a/models/workflow_execution/workflow_execution.go b/models/workflow_execution/workflow_execution.go index e802b29..1e883d6 100644 --- a/models/workflow_execution/workflow_execution.go +++ b/models/workflow_execution/workflow_execution.go @@ -1,124 +1,136 @@ package workflow_execution import ( - "encoding/json" "strings" "time" + "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" + "cloud.o-forge.io/core/oc-lib/models/resources" "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" ) -// ScheduledType - Enum for the different states of a workflow execution -type ScheduledType int - -const ( - SCHEDULED ScheduledType = iota + 1 - STARTED - FAILURE - SUCCESS - FORGOTTEN -) - -var str = [...]string{ - "scheduled", - "started", - "failure", - "success", - "forgotten", -} - -func FromInt(i int) string { - return str[i] -} - -func (d ScheduledType) String() string { - return str[d] -} - -// EnumIndex - Creating common behavior - give the type a EnumIndex functio -func (d ScheduledType) EnumIndex() int { - return int(d) -} - /* * WorkflowExecutions is a struct that represents a list of workflow executions * Warning: No user can write (del, post, put) a workflow execution, it is only used by the system * workflows generate their own executions */ type WorkflowExecutions struct { - WorkflowID string `json:"workflow_id" bson:"workflow_id"` - ResourceID string `json:"resource_id" bson:"resource_id"` - Executions []*WorkflowExecution `json:"executions" bson:"executions"` + utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) + ExecDate time.Time `json:"execution_date,omitempty" bson:"execution_date,omitempty" validate:"required"` // ExecDate is the execution date of the workflow, is required + EndDate *time.Time `json:"end_date,omitempty" bson:"end_date,omitempty"` // EndDate is the end date of the workflow + State common.ScheduledType `json:"state" bson:"state" default:"0"` // State is the state of the workflow + WorkflowID string `json:"workflow_id" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow } -// New - Creates a new instance of the WorkflowExecutions from a map -func (dma *WorkflowExecutions) Deserialize(j map[string]interface{}) *WorkflowExecutions { - b, err := json.Marshal(j) - if err != nil { - return nil +func (r *WorkflowExecutions) StoreDraftDefault() { + r.IsDraft = true +} + +func (r *WorkflowExecutions) CanUpdate(set utils.DBObject) (bool, utils.DBObject) { + if r.State != set.(*WorkflowExecutions).State { + return true, &WorkflowExecutions{State: set.(*WorkflowExecutions).State} // only state can be updated } - json.Unmarshal(b, dma) - return dma + return r.IsDraft, set // only draft buying can be updated } -// Serialize - Returns the WorkflowExecutions as a map -func (dma *WorkflowExecutions) Serialize() map[string]interface{} { - var m map[string]interface{} - b, err := json.Marshal(dma) - if err != nil { - return nil +func (r *WorkflowExecutions) CanDelete() bool { + return r.IsDraft // only draft bookings can be deleted +} + +func (wfa *WorkflowExecutions) Equals(we *WorkflowExecutions) bool { + return wfa.ExecDate.Equal(we.ExecDate) && wfa.WorkflowID == we.WorkflowID +} + +func (ws *WorkflowExecutions) PurgeDraft(request *tools.APIRequest) error { + if ws.EndDate == nil { + // if no end... then Book like a savage + e := ws.ExecDate.Add(time.Hour) + ws.EndDate = &e } - json.Unmarshal(b, &m) - return m -} - -/* -* WorkflowExecution is a struct that represents a workflow execution -* Warning: No user can write (del, post, put) a workflow execution, it is only used by the system -* workflows generate their own executions - */ -type WorkflowExecution struct { - utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name) - ExecDate *time.Time `json:"execution_date,omitempty" bson:"execution_date,omitempty" validate:"required"` // ExecDate is the execution date of the workflow, is required - EndDate *time.Time `json:"end_date,omitempty" bson:"end_date,omitempty"` // EndDate is the end date of the workflow - State ScheduledType `json:"state" bson:"state" default:"0"` // State is the state of the workflow - WorkflowID string `json:"workflow_id" bson:"workflow_id,omitempty"` // WorkflowID is the ID of the workflow -} - -func (wfa *WorkflowExecution) Equals(we *WorkflowExecution) bool { - return wfa.ExecDate.Equal(*we.ExecDate) && wfa.WorkflowID == we.WorkflowID + accessor := ws.GetAccessor(request) + res, code, err := accessor.Search(&dbs.Filters{ + And: map[string][]dbs.Filter{ // check if there is a booking on the same compute resource by filtering on the compute_resource_id, the state and the execution date + "state": {{Operator: dbs.EQUAL.String(), Value: common.DRAFT.EnumIndex()}}, + "workflow_id": {{Operator: dbs.EQUAL.String(), Value: ws.WorkflowID}}, + "execution_date": { + {Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(*ws.EndDate)}, + {Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(ws.ExecDate)}, + }, + }, + }, "", ws.IsDraft) + if code != 200 || err != nil { + return err + } + for _, r := range res { + accessor.DeleteOne(r.GetID()) + } + return nil } // tool to transform the argo status to a state -func (wfa *WorkflowExecution) ArgoStatusToState(status string) *WorkflowExecution { +func (wfa *WorkflowExecutions) ArgoStatusToState(status string) *WorkflowExecutions { status = strings.ToLower(status) switch status { case "succeeded": // Succeeded - wfa.State = SUCCESS + wfa.State = common.SUCCESS case "pending": // Pending - wfa.State = SCHEDULED + wfa.State = common.SCHEDULED case "running": // Running - wfa.State = STARTED + wfa.State = common.STARTED default: // Failed - wfa.State = FAILURE + wfa.State = common.FAILURE } return wfa } -func (r *WorkflowExecution) GenerateID() { +func (r *WorkflowExecutions) GenerateID() { r.UUID = uuid.New().String() } -func (d *WorkflowExecution) GetName() string { +func (d *WorkflowExecutions) GetName() string { return d.UUID + "_" + d.ExecDate.String() } -func (d *WorkflowExecution) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New(tools.WORKFLOW_EXECUTION, username, peerID, groups, caller) // Create a new instance of the accessor +func (d *WorkflowExecutions) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewShallowAccessor(request) // Create a new instance of the accessor } -func (d *WorkflowExecution) VerifyAuth(username string, peerID string, groups []string) bool { +func (d *WorkflowExecutions) VerifyAuth(request *tools.APIRequest) bool { return true } + +func (d *WorkflowExecutions) ToBookings(wf *workflow.Workflow) []*booking.Booking { + booking := []*booking.Booking{} + for _, p := range wf.ProcessingResources { + booking = append(booking, d.toItemBooking(wf.GetByRelatedProcessing(p.GetID(), wf.IsStorage))...) + booking = append(booking, d.toItemBooking(wf.GetByRelatedProcessing(p.GetID(), wf.IsProcessing))...) + } + return booking +} + +func (d *WorkflowExecutions) toItemBooking(ss []resources.ShallowResourceInterface) []*booking.Booking { + items := []*booking.Booking{} + for _, s := range ss { + start := d.ExecDate + if s := s.GetLocationStart(); s != nil { + start = *s + } + end := start.Add(time.Duration(s.GetExplicitDurationInS()) * time.Second) + bookingItem := &booking.Booking{ + State: common.DRAFT, + ResourceID: s.GetID(), + ResourceType: s.GetType(), + DestPeerID: s.GetCreatorID(), + ExpectedStartDate: start, + ExpectedEndDate: &end, + } + items = append(items, bookingItem) + } + return items +} diff --git a/models/workflow_execution/workflow_execution_mongo_accessor.go b/models/workflow_execution/workflow_execution_mongo_accessor.go index 9254459..d77faa7 100644 --- a/models/workflow_execution/workflow_execution_mongo_accessor.go +++ b/models/workflow_execution/workflow_execution_mongo_accessor.go @@ -1,10 +1,12 @@ package workflow_execution import ( + "errors" "time" "cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/logs" + "cloud.o-forge.io/core/oc-lib/models/common" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" ) @@ -13,57 +15,62 @@ type workflowExecutionMongoAccessor struct { utils.AbstractAccessor } -func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *workflowExecutionMongoAccessor { +func NewAccessor(request *tools.APIRequest) *workflowExecutionMongoAccessor { return &workflowExecutionMongoAccessor{ utils.AbstractAccessor{ - Logger: logs.CreateLogger(t.String()), // Create a logger with the data type - Caller: caller, - PeerID: peerID, - User: username, // Set the caller - Groups: groups, // Set the caller - Type: t, + Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type + Request: request, + Type: tools.WORKFLOW_EXECUTION, }, } } func (wfa *workflowExecutionMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { - return utils.GenericDeleteOne(id, wfa) + return nil, 404, errors.New("not implemented") } func (wfa *workflowExecutionMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { - return utils.GenericUpdateOne(set, id, wfa, &WorkflowExecution{}) + return nil, 404, errors.New("not implemented") } func (wfa *workflowExecutionMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { - return utils.GenericStoreOne(data, wfa) + return nil, 404, errors.New("not implemented") } func (wfa *workflowExecutionMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { - return utils.GenericStoreOne(data, wfa) + return nil, 404, errors.New("not implemented") } func (a *workflowExecutionMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { - return utils.GenericLoadOne[*WorkflowExecution](id, func(d utils.DBObject) (utils.DBObject, int, error) { - if d.(*WorkflowExecution).State == SCHEDULED && time.Now().UTC().After(*d.(*WorkflowExecution).ExecDate) { - d.(*WorkflowExecution).State = FORGOTTEN + return utils.GenericLoadOne[*WorkflowExecutions](id, func(d utils.DBObject) (utils.DBObject, int, error) { + if d.(*WorkflowExecutions).State == common.DRAFT && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) { + utils.GenericDeleteOne(d.GetID(), a) + return nil, 404, errors.New("Not found") + } + if d.(*WorkflowExecutions).State == common.SCHEDULED && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) { + d.(*WorkflowExecutions).State = common.FORGOTTEN utils.GenericRawUpdateOne(d, id, a) } return d, 200, nil }, a) } -func (a *workflowExecutionMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { - return utils.GenericLoadAll[*WorkflowExecution](a.getExec(), a) +func (a *workflowExecutionMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericLoadAll[*WorkflowExecutions](a.getExec(), isDraft, a) } -func (a *workflowExecutionMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { - return utils.GenericSearch[*WorkflowExecution](filters, search, (&WorkflowExecution{}).GetObjectFilters(search), a.getExec(), a) +func (a *workflowExecutionMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { + return utils.GenericSearch[*WorkflowExecutions](filters, search, (&WorkflowExecutions{}).GetObjectFilters(search), a.getExec(), isDraft, a) } func (a *workflowExecutionMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject { return func(d utils.DBObject) utils.ShallowDBObject { - if d.(*WorkflowExecution).State == SCHEDULED && time.Now().UTC().After(*d.(*WorkflowExecution).ExecDate) { - d.(*WorkflowExecution).State = FORGOTTEN + if d.(*WorkflowExecutions).State == common.DRAFT && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) { + utils.GenericDeleteOne(d.GetID(), a) + return nil + } + if d.(*WorkflowExecutions).State == common.SCHEDULED && time.Now().UTC().After(d.(*WorkflowExecutions).ExecDate) { + d.(*WorkflowExecutions).State = common.FORGOTTEN utils.GenericRawUpdateOne(d, d.GetID(), a) } return d diff --git a/models/workflow_execution/workflow_scheduler.go b/models/workflow_execution/workflow_scheduler.go new file mode 100644 index 0000000..3625b25 --- /dev/null +++ b/models/workflow_execution/workflow_scheduler.go @@ -0,0 +1,249 @@ +package workflow_execution + +import ( + "errors" + "fmt" + "strings" + "time" + + "cloud.o-forge.io/core/oc-lib/models/common" + "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/utils" + "cloud.o-forge.io/core/oc-lib/models/workflow" + "cloud.o-forge.io/core/oc-lib/models/workflow/graph" + "cloud.o-forge.io/core/oc-lib/tools" + "github.com/robfig/cron" +) + +type Schedule struct { + Start time.Time + End *time.Time +} + +/* +* 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 { + Workflow *workflow.Workflow `json:"workflow,omitempty"` // Workflow is the workflow dependancy of the schedule + WorkflowExecutions []*WorkflowExecutions `json:"workflow_executions,omitempty"` // WorkflowExecutions 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 +} + +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{ + 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) CheckBooking(wfID string, caller *tools.HTTPCaller) (bool, *workflow.Workflow, []*WorkflowExecutions, error) { + if caller == nil && caller.URLS == nil && caller.URLS[tools.BOOKING] == nil || caller.URLS[tools.BOOKING][tools.POST] == "" { + return false, nil, []*WorkflowExecutions{}, errors.New("no caller defined") + } + access := workflow.NewAccessor(nil) + res, code, err := access.LoadOne(wfID) + if code != 200 { + return false, nil, []*WorkflowExecutions{}, errors.New("could not load the workflow with id: " + err.Error()) + } + wf := res.(*workflow.Workflow) + wf, err = ws.planifyWorkflow(wf) + if err != nil { + return false, wf, []*WorkflowExecutions{}, err + } + ws.DurationS = wf.GetLongestTime(ws.End) + 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(wf.GetLongestTime(ws.End))*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, []*WorkflowExecutions{}, err + } + for _, exec := range execs { + bookings := exec.ToBookings(wf) + for _, booking := range bookings { + _, err := (&peer.Peer{}).LaunchPeerExecution(booking.DestPeerID, "", + tools.BOOKING, tools.POSTCHECK, booking.Serialize(booking), caller) + if err != nil { + return false, wf, execs, err + } + } + } + return true, wf, execs, nil +} + +func (ws *WorkflowSchedule) Schedules(wfID string, request *tools.APIRequest) (*workflow.Workflow, []*WorkflowExecutions, error) { + if request == nil { + return nil, []*WorkflowExecutions{}, errors.New("no request found") + } + c := request.Caller + if c == nil || c.URLS == nil || c.URLS[tools.BOOKING] == nil { + return nil, []*WorkflowExecutions{}, errors.New("no caller defined") + } + methods := c.URLS[tools.BOOKING] + if _, ok := methods[tools.POST]; !ok { + return nil, []*WorkflowExecutions{}, errors.New("no path found") + } + ok, wf, executions, err := ws.CheckBooking(wfID, request.Caller) + if !ok || err != nil { + return nil, []*WorkflowExecutions{}, errors.New("could not book the workflow" + fmt.Sprintf("%v", err)) + } + + ws.Workflow = wf + ws.WorkflowExecutions = executions + + for _, exec := range executions { + err := exec.PurgeDraft(request) + if err != nil { + return nil, []*WorkflowExecutions{}, errors.New("could not book the workflow" + fmt.Sprintf("%v", err)) + } + exec.GenerateID() + // Should DELETE the previous execution2 + utils.GenericStoreOne(exec, NewAccessor(request)) + } + return wf, executions, nil +} + +func (ws *WorkflowSchedule) planifyWorkflow(wf *workflow.Workflow) (*workflow.Workflow, error) { + processings := []*resources.CustomizedProcessingResource{} + for _, item := range wf.GetGraphItems(wf.IsProcessing) { + realItem := item.GetResource().(*resources.CustomizedProcessingResource) + timeFromStartS := wf.Graph.GetAverageTimeProcessingBeforeStart(0, realItem.GetID()) + started := ws.Start.Add(time.Duration(timeFromStartS) * time.Second) + wf.Graph.SetItemStartUsage(item.ID, started) + wf.Graph.SetItemEndUsage(item.ID, started.Add(time.Duration(realItem.ExplicitBookingDurationS))) + processings = append(processings, realItem) + } + for _, item := range wf.GetGraphItems(wf.IsData) { + wf.Graph.SetItemStartUsage(item.ID, ws.Start) + wf.Graph.SetItemEndUsage(item.ID, *ws.End) + } + for _, f := range []func(graph.GraphItem) bool{wf.IsStorage, wf.IsCompute} { + for _, item := range wf.GetGraphItems(f) { + nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(ws.Start, processings, item.GetResource(), + func(i graph.GraphItem) resources.ShallowResourceInterface { + if f(i) { + return i.GetResource() + } else { + return nil + } + }) + started := ws.Start.Add(time.Duration(nearestStart) * time.Second) + wf.Graph.SetItemStartUsage(item.ID, started) + if longestDuration >= 0 { + wf.Graph.SetItemEndUsage(item.ID, started.Add(time.Duration(longestDuration))) + } + } + } + for _, item := range wf.GetGraphItems(wf.IsWorkflow) { + access := workflow.NewAccessor(nil) + res, code, err := access.LoadOne(item.GetResource().GetID()) + if code != 200 || err != nil { + return nil, errors.New("could not load the workflow with id: " + fmt.Sprintf("%v", err.Error())) + } + innerWF := res.(*workflow.Workflow) + innerWF, err = ws.planifyWorkflow(innerWF) + started := ws.Start.Add(time.Duration(innerWF.GetNearestStart(ws.Start)) * time.Second) + wf.Graph.SetItemStartUsage(item.ID, started) + durationE := time.Duration(innerWF.GetLongestTime(ws.End)) + if durationE < 0 { + continue + } + ended := ws.Start.Add(durationE * time.Second) + wf.Graph.SetItemEndUsage(item.ID, ended) + } + return wf, 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) ([]*WorkflowExecutions, error) { + workflows_executions := []*WorkflowExecutions{} + dates, err := ws.getDates() + if err != nil { + return workflows_executions, err + } + for _, date := range dates { + obj := &WorkflowExecutions{ + AbstractObject: utils.AbstractObject{ + Name: workflow.Name + "_execution_" + date.Start.String(), // set the name of the execution + }, + ExecDate: date.Start, // set the execution date + EndDate: date.End, // set the end date + State: common.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 +} + +/* +* TODO : LARGEST GRAIN PLANIFYING THE WORKFLOW WHEN OPTION IS SET +* SET PROTECTION BORDER TIME + */ diff --git a/models/workspace/workspace.go b/models/workspace/workspace.go index cff7246..1086eb6 100644 --- a/models/workspace/workspace.go +++ b/models/workspace/workspace.go @@ -1,6 +1,8 @@ package workspace import ( + "fmt" + "cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area" "cloud.o-forge.io/core/oc-lib/models/resources" "cloud.o-forge.io/core/oc-lib/models/utils" @@ -16,17 +18,20 @@ type Workspace struct { Shared string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workspace } -func (d *Workspace) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New(tools.WORKSPACE, username, peerID, groups, caller) // Create a new instance of the accessor +func (d *Workspace) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessor(request) // Create a new instance of the accessor } -func (ao *Workspace) VerifyAuth(username string, peerID string, groups []string) bool { +func (ao *Workspace) VerifyAuth(request *tools.APIRequest) bool { + fmt.Println("Workspace.VerifyAuth", ao.Shared) if ao.Shared != "" { - shared, code, _ := shallow_collaborative_area.New(tools.COLLABORATIVE_AREA, username, peerID, groups, nil).LoadOne(ao.Shared) + shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(ao.Shared) + fmt.Println("Workspace.VerifyAuth", shared, code) if code != 200 || shared == nil { return false } - return shared.VerifyAuth(username, peerID, groups) + return shared.VerifyAuth(request) } - return ao.AbstractObject.VerifyAuth(username, peerID, groups) + fmt.Println("Workspace.VerifyAuth", ao.AbstractObject.VerifyAuth(request)) + return ao.AbstractObject.VerifyAuth(request) } diff --git a/models/workspace/workspace_history_mongo_accessor.go b/models/workspace/workspace_history_mongo_accessor.go index ab38f4f..061d0da 100644 --- a/models/workspace/workspace_history_mongo_accessor.go +++ b/models/workspace/workspace_history_mongo_accessor.go @@ -8,8 +8,8 @@ import ( type WorkspaceHistory struct{ Workspace } -func (d *WorkspaceHistory) GetAccessor(username string, peerID string, groups []string, caller *tools.HTTPCaller) utils.Accessor { - return New(tools.WORKFLOW_HISTORY, username, peerID, groups, caller) // Create a new instance of the accessor +func (d *WorkspaceHistory) GetAccessor(request *tools.APIRequest) utils.Accessor { + return NewAccessorHistory(request) // Create a new instance of the accessor } func (r *WorkspaceHistory) GenerateID() { r.UUID = uuid.New().String() diff --git a/models/workspace/workspace_mongo_accessor.go b/models/workspace/workspace_mongo_accessor.go index e1adb93..6acfe18 100644 --- a/models/workspace/workspace_mongo_accessor.go +++ b/models/workspace/workspace_mongo_accessor.go @@ -18,15 +18,21 @@ type workspaceMongoAccessor struct { } // New creates a new instance of the workspaceMongoAccessor -func New(t tools.DataType, username string, peerID string, groups []string, caller *tools.HTTPCaller) *workspaceMongoAccessor { +func NewAccessorHistory(request *tools.APIRequest) *workspaceMongoAccessor { + return new(tools.WORKSPACE_HISTORY, request) +} + +func NewAccessor(request *tools.APIRequest) *workspaceMongoAccessor { + return new(tools.WORKSPACE, request) +} + +// New creates a new instance of the workspaceMongoAccessor +func new(t tools.DataType, request *tools.APIRequest) *workspaceMongoAccessor { return &workspaceMongoAccessor{ utils.AbstractAccessor{ - Logger: logs.CreateLogger(t.String()), // Create a logger with the data type - Caller: caller, - PeerID: peerID, - User: username, - Groups: groups, // Set the caller - Type: t, + Logger: logs.CreateLogger(t.String()), // Create a logger with the data type + Request: request, + Type: t, }, } } @@ -36,7 +42,7 @@ func New(t tools.DataType, username string, peerID string, groups []string, call func (a *workspaceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { res, code, err := utils.GenericDeleteOne(id, a) if code == 200 && res != nil { - a.share(res.(*Workspace), tools.DELETE, a.Caller) // Share the deletion to the peers + a.share(res.(*Workspace), tools.DELETE, a.GetCaller()) // Share the deletion to the peers } return res, code, err } @@ -46,7 +52,7 @@ func (a *workspaceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils d := set.(*Workspace) // Get the workspace from the set d.Clear() if d.Active { // If the workspace is active, deactivate all the other workspaces - res, _, err := a.LoadAll() + res, _, err := a.LoadAll(true) if err == nil { for _, r := range res { if r.GetID() != id { @@ -58,7 +64,7 @@ func (a *workspaceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils } res, code, err := utils.GenericUpdateOne(set, id, a, &Workspace{}) if code == 200 && res != nil { - a.share(res.(*Workspace), tools.PUT, a.Caller) + a.share(res.(*Workspace), tools.PUT, a.GetCaller()) } return res, code, err } @@ -70,8 +76,8 @@ func (a *workspaceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: data.GetName() + "_workspace"}}, }, } - res, _, err := a.Search(filters, "") // Search for the workspace - if err == nil && len(res) > 0 { // If the workspace already exists, return an error + res, _, err := a.Search(filters, "", true) // Search for the workspace + if err == nil && len(res) > 0 { // If the workspace already exists, return an error return nil, 409, errors.New("A workspace with the same name already exists") } // reset the resources @@ -87,23 +93,23 @@ func (a *workspaceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, i func (a *workspaceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { return utils.GenericLoadOne[*Workspace](id, func(d utils.DBObject) (utils.DBObject, int, error) { - d.(*Workspace).Fill(a.GetUser(), a.PeerID, a.Groups) + d.(*Workspace).Fill(a.GetRequest()) return d, 200, nil }, a) } -func (a *workspaceMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) { +func (a *workspaceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { return utils.GenericLoadAll[*Workspace](func(d utils.DBObject) utils.ShallowDBObject { - d.(*Workspace).Fill(a.GetUser(), a.PeerID, a.Groups) + d.(*Workspace).Fill(a.GetRequest()) return d - }, a) + }, isDraft, a) } -func (a *workspaceMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) { +func (a *workspaceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { return utils.GenericSearch[*Workspace](filters, search, (&Workspace{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { - d.(*Workspace).Fill(a.GetUser(), a.PeerID, a.Groups) + d.(*Workspace).Fill(a.GetRequest()) return d - }, a) + }, isDraft, a) } /* @@ -115,7 +121,7 @@ func (a *workspaceMongoAccessor) share(realData *Workspace, method tools.METHOD, return } shallow := &shallow_collaborative_area.ShallowCollaborativeArea{} - access := (shallow).GetAccessor(a.GetUser(), a.PeerID, a.Groups, nil) + access := (shallow).GetAccessor(a.GetRequest()) res, code, _ := access.LoadOne(realData.Shared) if code != 200 { return diff --git a/tools/api.go b/tools/api.go index 9d5be05..4877a35 100644 --- a/tools/api.go +++ b/tools/api.go @@ -11,6 +11,13 @@ import ( beego "github.com/beego/beego/v2/server/web" ) +type APIRequest struct { + Username string + PeerID string + Groups []string + Caller *HTTPCaller +} + /* * API is the Health Check API * it defines the health check methods diff --git a/tools/enums.go b/tools/enums.go index c1395b8..9f61c9a 100644 --- a/tools/enums.go +++ b/tools/enums.go @@ -20,6 +20,8 @@ const ( BOOKING WORKFLOW_HISTORY WORKSPACE_HISTORY + ORDER + BUYING_STATUS ) var NOAPI = "" @@ -48,6 +50,8 @@ var DefaultAPI = [...]string{ DATACENTERAPI, NOAPI, NOAPI, + NOAPI, + NOAPI, } // Bind the standard data name to the data type @@ -68,6 +72,8 @@ var Str = [...]string{ "booking", "workflow_history", "workspace_history", + "order", + "buying_status", } func FromInt(i int) string { diff --git a/tools/remote_caller.go b/tools/remote_caller.go index b7eb5de..32d46da 100644 --- a/tools/remote_caller.go +++ b/tools/remote_caller.go @@ -16,6 +16,7 @@ const ( GET METHOD = iota PUT POST + POSTCHECK DELETE STRICT_INTERNAL_GET @@ -26,7 +27,7 @@ const ( // String returns the string of the enum func (m METHOD) String() string { - return [...]string{"GET", "PUT", "POST", "DELETE", "INTERNALGET", "INTERNALPUT", "INTERNALPOST", "INTERNALDELETE"}[m] + return [...]string{"GET", "PUT", "POST", "POST", "DELETE", "INTERNALGET", "INTERNALPUT", "INTERNALPOST", "INTERNALDELETE"}[m] } // EnumIndex returns the index of the enum @@ -36,7 +37,7 @@ func (m METHOD) EnumIndex() int { // ToMethod returns the method from a string func ToMethod(str string) METHOD { - for _, s := range []METHOD{GET, PUT, POST, DELETE, + for _, s := range []METHOD{GET, PUT, POST, POSTCHECK, DELETE, STRICT_INTERNAL_GET, STRICT_INTERNAL_PUT, STRICT_INTERNAL_POST, STRICT_INTERNAL_DELETE} { if s.String() == str { return s