Files
oc-datacenter/infrastructure/storage/pvc_setter.go
2026-03-24 10:50:36 +01:00

175 lines
5.6 KiB
Go

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
}