2026-03-24 10:50:36 +01:00
|
|
|
package storage
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"slices"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"oc-datacenter/conf"
|
|
|
|
|
|
|
|
|
|
oclib "cloud.o-forge.io/core/oc-lib"
|
2026-03-25 11:11:03 +01:00
|
|
|
"cloud.o-forge.io/core/oc-lib/dbs"
|
|
|
|
|
bookingmodel "cloud.o-forge.io/core/oc-lib/models/booking"
|
2026-03-24 10:50:36 +01:00
|
|
|
"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
|
2026-03-25 11:11:03 +01:00
|
|
|
// ClaimSuffix overrides ExecutionsID as the suffix in ClaimName when non-empty.
|
|
|
|
|
// Used when the PVC namespace differs from the claim name suffix (Admiralty target).
|
|
|
|
|
ClaimSuffix string
|
2026-03-24 10:50:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewPVCSetter(execID, storageID string) *PVCSetter {
|
|
|
|
|
return &PVCSetter{ExecutionsID: execID, StorageID: storageID}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 11:11:03 +01:00
|
|
|
// NewPVCSetterWithClaimSuffix creates a PVCSetter where the claim name suffix
|
|
|
|
|
// differs from the execution namespace (e.g. Admiralty target provisioning).
|
|
|
|
|
func NewPVCSetterWithClaimSuffix(storageID, claimSuffix string) *PVCSetter {
|
|
|
|
|
return &PVCSetter{StorageID: storageID, ClaimSuffix: claimSuffix}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *PVCSetter) emitConsiders(executionsID, originID string, provErr error, self bool) {
|
2026-03-24 10:50:36 +01:00
|
|
|
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())
|
2026-03-25 11:11:03 +01:00
|
|
|
p.emitConsiders(event.ExecutionsID, event.OriginID, err, self)
|
2026-03-24 10:50:36 +01:00
|
|
|
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())
|
2026-03-25 11:11:03 +01:00
|
|
|
p.emitConsiders(event.ExecutionsID, event.OriginID, err, self)
|
2026-03-24 10:50:36 +01:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 11:11:03 +01:00
|
|
|
claimSuffix := event.ExecutionsID
|
|
|
|
|
if p.ClaimSuffix != "" {
|
|
|
|
|
claimSuffix = p.ClaimSuffix
|
|
|
|
|
}
|
|
|
|
|
claimName := ClaimName(event.StorageName, claimSuffix)
|
2026-03-24 10:50:36 +01:00
|
|
|
if err := k.CreatePVC(ctx, claimName, event.ExecutionsID, sizeStr); err != nil {
|
|
|
|
|
logger.Error().Msg("PVCSetter.InitializeAsSource: failed to create PVC: " + err.Error())
|
2026-03-25 11:11:03 +01:00
|
|
|
p.emitConsiders(event.ExecutionsID, event.OriginID, err, self)
|
2026-03-24 10:50:36 +01:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.Info().Msg("PVCSetter.InitializeAsSource: PVC " + claimName + " created in " + event.ExecutionsID)
|
2026-03-25 11:11:03 +01:00
|
|
|
p.emitConsiders(event.ExecutionsID, event.OriginID, nil, self)
|
2026-03-24 10:50:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
2026-03-25 11:11:03 +01:00
|
|
|
|
|
|
|
|
// teardownPVCForExecution deletes all local PVCs provisioned for the execution.
|
|
|
|
|
// It searches LIVE_STORAGE bookings and resolves the storage name via the live storage.
|
|
|
|
|
func (p *PVCSetter) TeardownForExecution(ctx context.Context, localPeerID string) {
|
|
|
|
|
logger := oclib.GetLogger()
|
|
|
|
|
|
|
|
|
|
res := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), "", localPeerID, []string{}, nil).
|
|
|
|
|
Search(&dbs.Filters{
|
|
|
|
|
And: map[string][]dbs.Filter{
|
|
|
|
|
"executions_id": {{Operator: dbs.EQUAL.String(), Value: p.ExecutionsID}},
|
|
|
|
|
"resource_type": {{Operator: dbs.EQUAL.String(), Value: tools.LIVE_STORAGE.EnumIndex()}},
|
|
|
|
|
},
|
|
|
|
|
}, "", false)
|
|
|
|
|
|
|
|
|
|
if res.Err != "" || len(res.Data) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, dbo := range res.Data {
|
|
|
|
|
b, ok := dbo.(*bookingmodel.Booking)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// Resolve storage name from live storage to compute the claim name.
|
|
|
|
|
storageName := ResolveStorageName(b.ResourceID, localPeerID)
|
|
|
|
|
if storageName == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
logger.Info().Msgf("InfraTeardown: PVC teardown exec=%s storage=%s", p.ExecutionsID, b.ResourceID)
|
|
|
|
|
event := PVCDeleteEvent{
|
|
|
|
|
ExecutionsID: p.ExecutionsID,
|
|
|
|
|
StorageID: b.ResourceID,
|
|
|
|
|
StorageName: storageName,
|
|
|
|
|
SourcePeerID: localPeerID,
|
|
|
|
|
DestPeerID: b.DestPeerID,
|
|
|
|
|
OriginID: "",
|
|
|
|
|
}
|
|
|
|
|
p.StorageID = b.ResourceID
|
|
|
|
|
p.TeardownAsSource(ctx, event)
|
|
|
|
|
}
|
|
|
|
|
}
|