package storage import ( "context" "encoding/json" "fmt" "slices" "strings" "oc-datacenter/conf" oclib "cloud.o-forge.io/core/oc-lib" "cloud.o-forge.io/core/oc-lib/models/live" "cloud.o-forge.io/core/oc-lib/tools" ) // PVCProvisionEvent is the NATS payload for local PVC provisioning. // Same-peer deployments are handled directly; cross-peer routes via PB_PVC_CONFIG. type PVCProvisionEvent struct { ExecutionsID string `json:"executions_id"` StorageID string `json:"storage_id"` StorageName string `json:"storage_name"` SourcePeerID string `json:"source_peer_id"` DestPeerID string `json:"dest_peer_id"` OriginID string `json:"origin_id"` } // PVCDeleteEvent is the NATS payload for local PVC teardown. type PVCDeleteEvent struct { ExecutionsID string `json:"executions_id"` StorageID string `json:"storage_id"` StorageName string `json:"storage_name"` SourcePeerID string `json:"source_peer_id"` DestPeerID string `json:"dest_peer_id"` OriginID string `json:"origin_id"` } // ClaimName returns the deterministic PVC name shared by oc-datacenter and oc-monitord. func ClaimName(storageName, executionsID string) string { return strings.ReplaceAll(strings.ToLower(storageName), " ", "-") + "-" + executionsID } // PVCSetter carries the execution context for a local PVC provisioning. type PVCSetter struct { ExecutionsID string StorageID string } func NewPVCSetter(execID, storageID string) *PVCSetter { return &PVCSetter{ExecutionsID: execID, StorageID: storageID} } func emitConsiders(executionsID, originID string, provErr error, self bool) { type pvcConsidersPayload struct { OriginID string `json:"origin_id"` ExecutionsID string `json:"executions_id"` Error *string `json:"error,omitempty"` } var errStr *string if provErr != nil { s := provErr.Error() errStr = &s } payload, _ := json.Marshal(pvcConsidersPayload{ OriginID: originID, ExecutionsID: executionsID, Error: errStr, }) if self { go tools.NewNATSCaller().SetNATSPub(tools.CONSIDERS_EVENT, tools.NATSResponse{ FromApp: "oc-datacenter", Datatype: tools.STORAGE_RESOURCE, Method: int(tools.CONSIDERS_EVENT), Payload: payload, }) return } b, _ := json.Marshal(&tools.PropalgationMessage{ DataType: tools.STORAGE_RESOURCE.EnumIndex(), Action: tools.PB_CONSIDERS, Payload: payload, }) go tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{ FromApp: "oc-datacenter", Datatype: -1, Method: int(tools.PROPALGATION_EVENT), Payload: b, }) } // InitializeAsSource creates the PVC in the execution namespace on the local cluster. // self must be true when source and dest are the same peer (direct CONSIDERS_EVENT emission). func (p *PVCSetter) InitializeAsSource(ctx context.Context, event PVCProvisionEvent, self bool) { logger := oclib.GetLogger() sizeStr, err := p.loadStorageSize(event.SourcePeerID) if err != nil { logger.Error().Msg("PVCSetter.InitializeAsSource: " + err.Error()) emitConsiders(event.ExecutionsID, event.OriginID, err, self) return } k, err := tools.NewKubernetesService( conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort, conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData, ) if err != nil { logger.Error().Msg("PVCSetter.InitializeAsSource: failed to create k8s service: " + err.Error()) emitConsiders(event.ExecutionsID, event.OriginID, err, self) return } claimName := ClaimName(event.StorageName, event.ExecutionsID) if err := k.CreatePVC(ctx, claimName, event.ExecutionsID, sizeStr); err != nil { logger.Error().Msg("PVCSetter.InitializeAsSource: failed to create PVC: " + err.Error()) emitConsiders(event.ExecutionsID, event.OriginID, err, self) return } logger.Info().Msg("PVCSetter.InitializeAsSource: PVC " + claimName + " created in " + event.ExecutionsID) emitConsiders(event.ExecutionsID, event.OriginID, nil, self) } // TeardownAsSource deletes the PVC from the execution namespace. func (p *PVCSetter) TeardownAsSource(ctx context.Context, event PVCDeleteEvent) { logger := oclib.GetLogger() k, err := tools.NewKubernetesService( conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort, conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData, ) if err != nil { logger.Error().Msg("PVCSetter.TeardownAsSource: failed to create k8s service: " + err.Error()) return } claimName := ClaimName(event.StorageName, event.ExecutionsID) if err := k.DeletePVC(ctx, claimName, event.ExecutionsID); err != nil { logger.Error().Msg("PVCSetter.TeardownAsSource: failed to delete PVC: " + err.Error()) return } logger.Info().Msg("PVCSetter.TeardownAsSource: PVC " + claimName + " deleted from " + event.ExecutionsID) } // ResolveStorageName returns the live storage name for a given storageID, or "" if not found. func ResolveStorageName(storageID, peerID string) string { res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), "", peerID, []string{}, nil).LoadAll(false) if res.Err != "" { return "" } for _, dbo := range res.Data { l := dbo.(*live.LiveStorage) if slices.Contains(l.ResourcesID, storageID) { return l.GetName() } } return "" } // loadStorageSize looks up the SizeGB for this storage in live storages. func (p *PVCSetter) loadStorageSize(peerID string) (string, error) { res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), "", peerID, []string{}, nil).LoadAll(false) if res.Err != "" { return "", fmt.Errorf("loadStorageSize: %s", res.Err) } for _, dbo := range res.Data { l := dbo.(*live.LiveStorage) if slices.Contains(l.ResourcesID, p.StorageID) && l.SizeGB > 0 { return fmt.Sprintf("%dGi", l.SizeGB), nil } } return "10Gi", nil }