diff --git a/models/organization/organization.go b/models/organization/organization.go new file mode 100644 index 0000000..cf23d7b --- /dev/null +++ b/models/organization/organization.go @@ -0,0 +1,11 @@ +package organization + +// Organization holds descriptive data about a peer's organization. +// It is optional — a peer without an organization has a nil Organization field. +type Organization struct { + Name string `json:"name,omitempty" bson:"name,omitempty"` + Description string `json:"description,omitempty" bson:"description,omitempty"` + Website string `json:"website,omitempty" bson:"website,omitempty"` + Sector string `json:"sector,omitempty" bson:"sector,omitempty"` + Country string `json:"country,omitempty" bson:"country,omitempty"` +} diff --git a/models/peer/peer.go b/models/peer/peer.go index 87f5d0c..19f2f27 100644 --- a/models/peer/peer.go +++ b/models/peer/peer.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "cloud.o-forge.io/core/oc-lib/models/organization" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" "github.com/biter777/countries" @@ -30,9 +31,19 @@ const ( NANO PENDING_NANO PENDING_MASTER + ORGANIZATION_MASTER + ORGANIZATION_MEMBER + ORGANIZATION_PARTNER + ORGANIZATION_MASTER_PENDING + ORGANIZATION_MEMBER_PENDING ) -var path = []string{"known", "self", "partner", "blacklist", "pending_partner", "master", "nano", "pending_nano", "pending_master"} +var path = []string{ + "known", "self", "partner", "blacklist", "pending_partner", + "master", "nano", "pending_nano", "pending_master", + "organization_master", "organization_member", "organization_partner", + "organization_master_pending", "organization_member_pending", +} func GetRelationPath(str string) int { for i, p := range path { @@ -121,6 +132,15 @@ type Peer struct { // When oc-discovery fails to reach a NANO, it routes the booking to MasterID instead. MasterID string `json:"master_id,omitempty" bson:"master_id,omitempty"` + // OrganizationMasterID is the MongoDB _id of the peer acting as this node's + // organization master. Set automatically when an ORGANIZATION_MASTER relation + // is validated (equivalent of MasterID for the Nano/Master hierarchy). + OrganizationMasterID string `json:"organization_master_id,omitempty" bson:"organization_master_id,omitempty"` + + // Organization holds optional descriptive data about the peer's organization. + // Null when the peer has not registered any organization data. + Organization *organization.Organization `json:"organization,omitempty" bson:"organization,omitempty"` + // Volatile connectivity state — never persisted to DB (bson:"-"). // Set in-memory by oc-peer when it receives a PEER_OBSERVE_RESPONSE_EVENT. // Considered offline when LastHeartbeat is older than 60 s (30 s interval + 30 s grace). diff --git a/models/resources/consent.go b/models/resources/consent.go new file mode 100644 index 0000000..cc75123 --- /dev/null +++ b/models/resources/consent.go @@ -0,0 +1,14 @@ +package resources + +// Consent represents a consent request attached to a resource. +// ConsentString is the question displayed to the user. +// Optional, when true, means the user may decline without blocking scheduling. +// A nil Optional is treated as required (false). +type Consent struct { + ConsentString string `json:"consent_string" bson:"consent_string"` + Optional *bool `json:"optional,omitempty" bson:"optional,omitempty"` +} + +func (c Consent) IsOptional() bool { + return c.Optional != nil && *c.Optional +} diff --git a/models/resources/interfaces.go b/models/resources/interfaces.go index e1033e4..b5e4d77 100755 --- a/models/resources/interfaces.go +++ b/models/resources/interfaces.go @@ -30,6 +30,7 @@ type ResourceInterface interface { GetInputs() []models.Param GetOutputs() []models.Param GetExploitationAuthorizations() []ExploitationAuthorization + GetConsents() []Consent } type ResourceInstanceITF interface { diff --git a/models/resources/resource.go b/models/resources/resource.go index 1cb8a08..2d0e4e2 100755 --- a/models/resources/resource.go +++ b/models/resources/resource.go @@ -50,6 +50,10 @@ type AbstractResource struct { // NOT in a separate collection. // Visibility-filtered per requesting peer before any response is sent. ExploitationAuthorizations []ExploitationAuthorization `json:"exploitation_authorizations,omitempty" bson:"exploitation_authorizations,omitempty"` + + // Consents lists the consent questions the user must acknowledge before + // scheduling this resource. Consents with Optional=true may be skipped. + Consents []Consent `json:"consents,omitempty" bson:"consents,omitempty"` } func (ri *AbstractResource) Extend(typ ...string) map[string][]tools.DataType { @@ -100,6 +104,11 @@ func (r *AbstractResource) GetExploitationAuthorizations() []ExploitationAuthori return r.ExploitationAuthorizations } +// GetConsents returns the consent questions declared by this resource. +func (r *AbstractResource) GetConsents() []Consent { + return r.Consents +} + // FilterExploitationAuthorizations removes AEs that are not visible to peerID. // Must be called before serializing the resource for a consumer peer. // The resource owner (CreatorID) always sees all AEs unfiltered. diff --git a/models/workflow_execution/workflow_execution.go b/models/workflow_execution/workflow_execution.go index 6cf6e61..ad4ff53 100755 --- a/models/workflow_execution/workflow_execution.go +++ b/models/workflow_execution/workflow_execution.go @@ -48,6 +48,10 @@ type WorkflowExecution struct { BookingsState map[string]BookingState `json:"bookings_state" bson:"bookings_state,omitempty"` // booking_id → reservation+completion status PurchasesState map[string]bool `json:"purchases_state" bson:"purchases_state,omitempty"` // purchase_id → confirmed + // ResourceConsents records which consent strings the user acknowledged per resource + // (resource_id → list of acknowledged ConsentString values) at scheduling time. + ResourceConsents map[string][]string `json:"resource_consents,omitempty" bson:"resource_consents,omitempty"` + // Graph is a lightweight, real-time summary of the workflow execution graph. // Keyed by workflow graph item ID; updated by oc-scheduler on each step-done event. // Consumed by oc-front to render the live execution panel via websocket updates. diff --git a/tools/enums.go b/tools/enums.go index ea4ddd1..5b76d36 100644 --- a/tools/enums.go +++ b/tools/enums.go @@ -208,6 +208,14 @@ const ( // for a private source resource (isReachable=false, Phase 4). // oc-discovery routes it to the resource owner peer via ProtocolSourcePresignResource. PB_SOURCE_PRESIGN + + // PB_ORG_PARTNER is propagated via PB_PROPAGATE through oc-discovery to the + // organization master's oc-discovery, which notifies its oc-peer via + // ORG_PARTNER_EVENT. The master's oc-peer confirms or rejects by emitting a + // PROPALGATION_EVENT back, which oc-discovery routes to the originating + // oc-discovery, which in turn notifies our oc-peer via ORG_PARTNER_EVENT to + // finalize the relation. + PB_ORG_PARTNER ) func GetActionString(ss string) PubSubAction { @@ -242,6 +250,8 @@ func GetActionString(ss string) PubSubAction { return PB_PROPAGATE case "source_presign": return PB_SOURCE_PRESIGN + case "org_partner": + return PB_ORG_PARTNER default: return NONE } @@ -264,8 +274,9 @@ var path = []string{ "none", // 12 NONE "observe", // 13 PB_OBSERVE "observe_close", // 14 PB_OBSERVE_CLOSE - "propagate", // 15 PB_PROPAGATE - "source_presign", // 16 PB_SOURCE_PRESIGN + "propagate", // 15 PB_PROPAGATE + "source_presign", // 16 PB_SOURCE_PRESIGN + "org_partner", // 17 PB_ORG_PARTNER } func (m PubSubAction) String() string { diff --git a/tools/nats_caller.go b/tools/nats_caller.go index 8cec732..ebaab3b 100644 --- a/tools/nats_caller.go +++ b/tools/nats_caller.go @@ -32,7 +32,7 @@ var meths = []string{"remove execution", "create execution", "planner execution" "considers event", "admiralty config event", "minio config event", "pvc config event", "workflow started event", "workflow step done event", "workflow done event", "peer behavior event", "peer observe response event", "peer observe event", - "source presign event", + "source presign event", "org partner event", } const ( @@ -85,6 +85,10 @@ const ( // oc-datacenter listens to it to generate a pre-signed Minio URL and reply // via PB_CONSIDERS (Phase 4 — isReachable=false). SOURCE_PRESIGN_EVENT + + // ORG_PARTNER_EVENT is emitted by a peer to its OrganizationMaster to ask: + // "is peer X one of your members?". The master replies via the same channel. + ORG_PARTNER_EVENT ) func (n NATSMethod) String() string { @@ -98,7 +102,7 @@ func NameToMethod(name string) NATSMethod { CONSIDERS_EVENT, ADMIRALTY_CONFIG_EVENT, MINIO_CONFIG_EVENT, PVC_CONFIG_EVENT, WORKFLOW_STARTED_EVENT, WORKFLOW_STEP_DONE_EVENT, WORKFLOW_DONE_EVENT, PEER_BEHAVIOR_EVENT, PEER_OBSERVE_RESPONSE_EVENT, PEER_OBSERVE_EVENT, - SOURCE_PRESIGN_EVENT} { + SOURCE_PRESIGN_EVENT, ORG_PARTNER_EVENT} { if strings.Contains(strings.ToLower(v.String()), strings.ToLower(name)) { return v }