Files
oc-discovery/daemons/node/stream/service.go

303 lines
9.5 KiB
Go
Raw Normal View History

2026-01-30 16:57:36 +01:00
package stream
import (
"context"
"encoding/json"
"fmt"
2026-02-03 15:25:15 +01:00
"oc-discovery/conf"
2026-01-30 16:57:36 +01:00
"oc-discovery/daemons/node/common"
2026-02-03 15:25:15 +01:00
"strings"
2026-01-30 16:57:36 +01:00
"sync"
"time"
oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/peer"
2026-02-03 15:25:15 +01:00
"cloud.o-forge.io/core/oc-lib/models/utils"
"github.com/google/uuid"
2026-01-30 16:57:36 +01:00
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
pp "github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
2026-02-17 13:11:22 +01:00
ma "github.com/multiformats/go-multiaddr"
2026-01-30 16:57:36 +01:00
)
2026-02-24 14:31:37 +01:00
const ProtocolConsidersResource = "/opencloud/resource/considers/1.0"
const ProtocolMinioConfigResource = "/opencloud/minio/config/1.0"
const ProtocolAdmiraltyConfigResource = "/opencloud/admiralty/config/1.0"
2026-01-30 16:57:36 +01:00
const ProtocolSearchResource = "/opencloud/resource/search/1.0"
const ProtocolCreateResource = "/opencloud/resource/create/1.0"
const ProtocolUpdateResource = "/opencloud/resource/update/1.0"
const ProtocolDeleteResource = "/opencloud/resource/delete/1.0"
2026-02-24 14:31:37 +01:00
const ProtocolSendPlanner = "/opencloud/resource/planner/1.0"
const ProtocolVerifyResource = "/opencloud/resource/verify/1.0"
2026-01-30 16:57:36 +01:00
const ProtocolHeartbeatPartner = "/opencloud/resource/heartbeat/partner/1.0"
2026-02-24 14:31:37 +01:00
var protocols = map[protocol.ID]*common.ProtocolInfo{
ProtocolConsidersResource: {WaitResponse: false, TTL: 3 * time.Second},
ProtocolSendPlanner: {WaitResponse: true, TTL: 24 * time.Hour},
ProtocolSearchResource: {WaitResponse: true, TTL: 1 * time.Minute},
ProtocolVerifyResource: {WaitResponse: true, TTL: 1 * time.Minute},
ProtocolMinioConfigResource: {WaitResponse: true, TTL: 1 * time.Minute},
ProtocolAdmiraltyConfigResource: {WaitResponse: true, TTL: 1 * time.Minute},
}
2026-02-24 14:31:37 +01:00
var protocolsPartners = map[protocol.ID]*common.ProtocolInfo{
ProtocolCreateResource: {TTL: 3 * time.Second},
ProtocolUpdateResource: {TTL: 3 * time.Second},
ProtocolDeleteResource: {TTL: 3 * time.Second},
2026-01-30 16:57:36 +01:00
}
type StreamService struct {
Key pp.ID
Host host.Host
Node common.DiscoveryPeer
Streams common.ProtocolStream
maxNodesConn int
2026-02-17 13:11:22 +01:00
Mu sync.RWMutex
2026-01-30 16:57:36 +01:00
// Stream map[protocol.ID]map[pp.ID]*daemons.Stream
}
func InitStream(ctx context.Context, h host.Host, key pp.ID, maxNode int, node common.DiscoveryPeer) (*StreamService, error) {
2026-02-02 09:05:58 +01:00
logger := oclib.GetLogger()
2026-01-30 16:57:36 +01:00
service := &StreamService{
Key: key,
Node: node,
Host: h,
Streams: common.ProtocolStream{},
maxNodesConn: maxNode,
}
2026-02-02 09:05:58 +01:00
logger.Info().Msg("handle to partner heartbeat protocol...")
2026-01-30 16:57:36 +01:00
service.Host.SetStreamHandler(ProtocolHeartbeatPartner, service.HandlePartnerHeartbeat)
for proto := range protocols {
service.Host.SetStreamHandler(proto, service.HandleResponse)
}
2026-02-02 09:05:58 +01:00
logger.Info().Msg("connect to partners...")
2026-01-30 16:57:36 +01:00
service.connectToPartners() // we set up a stream
go service.StartGC(8 * time.Second)
2026-01-30 16:57:36 +01:00
return service, nil
}
func (s *StreamService) HandleResponse(stream network.Stream) {
s.Mu.Lock()
stream.Protocol()
if s.Streams[stream.Protocol()] == nil {
s.Streams[stream.Protocol()] = map[pp.ID]*common.Stream{}
}
2026-02-24 14:31:37 +01:00
expiry := 1 * time.Minute
if protocols[stream.Protocol()] != nil {
expiry = protocols[stream.Protocol()].TTL
} else if protocolsPartners[stream.Protocol()] != nil {
expiry = protocolsPartners[stream.Protocol()].TTL
}
s.Streams[stream.Protocol()][stream.Conn().RemotePeer()] = &common.Stream{
Stream: stream,
2026-02-24 14:31:37 +01:00
Expiry: time.Now().UTC().Add(expiry + 1*time.Minute),
}
s.Mu.Unlock()
go s.readLoop(s.Streams[stream.Protocol()][stream.Conn().RemotePeer()],
stream.Conn().RemotePeer(),
stream.Protocol(), protocols[stream.Protocol()])
}
2026-01-30 16:57:36 +01:00
func (s *StreamService) HandlePartnerHeartbeat(stream network.Stream) {
2026-02-05 16:17:14 +01:00
s.Mu.Lock()
2026-02-03 15:25:15 +01:00
if s.Streams[ProtocolHeartbeatPartner] == nil {
2026-01-30 16:57:36 +01:00
s.Streams[ProtocolHeartbeatPartner] = map[pp.ID]*common.Stream{}
}
2026-02-03 15:25:15 +01:00
streams := s.Streams[ProtocolHeartbeatPartner]
2026-02-17 13:11:22 +01:00
streamsAnonym := map[pp.ID]common.HeartBeatStreamed{}
for k, v := range streams {
streamsAnonym[k] = v
}
s.Mu.Unlock()
pid, hb, err := common.CheckHeartbeat(s.Host, stream, streamsAnonym, &s.Mu, s.maxNodesConn)
if err != nil {
return
}
s.Mu.Lock()
defer s.Mu.Unlock()
2026-01-30 16:57:36 +01:00
// if record already seen update last seen
if rec, ok := streams[*pid]; ok {
rec.DID = hb.DID
rec.Expiry = time.Now().UTC().Add(10 * time.Second)
2026-01-30 16:57:36 +01:00
} else { // if not in stream ?
2026-02-17 13:11:22 +01:00
val, err := stream.Conn().RemoteMultiaddr().ValueForProtocol(ma.P_IP4)
2026-01-30 16:57:36 +01:00
if err == nil {
2026-02-17 13:11:22 +01:00
s.ConnectToPartner(val)
2026-01-30 16:57:36 +01:00
}
}
go s.StartGC(30 * time.Second)
}
func (s *StreamService) connectToPartners() error {
for proto, info := range protocolsPartners {
2026-02-05 11:23:11 +01:00
f := func(ss network.Stream) {
if s.Streams[proto] == nil {
s.Streams[proto] = map[pp.ID]*common.Stream{}
}
s.Streams[proto][ss.Conn().RemotePeer()] = &common.Stream{
Stream: ss,
Expiry: time.Now().UTC().Add(10 * time.Second),
2026-02-05 11:23:11 +01:00
}
go s.readLoop(s.Streams[proto][ss.Conn().RemotePeer()], ss.Conn().RemotePeer(), proto, info)
2026-02-05 11:23:11 +01:00
}
fmt.Println("SetStreamHandler", proto)
s.Host.SetStreamHandler(proto, f)
}
2026-01-30 16:57:36 +01:00
peers, err := s.searchPeer(fmt.Sprintf("%v", peer.PARTNER.EnumIndex()))
if err != nil {
return err
}
for _, p := range peers {
2026-02-17 13:11:22 +01:00
s.ConnectToPartner(p.StreamAddress)
2026-01-30 16:57:36 +01:00
}
return nil
}
2026-02-17 13:11:22 +01:00
func (s *StreamService) ConnectToPartner(address string) {
if ad, err := pp.AddrInfoFromString(address); err == nil {
common.SendHeartbeat(context.Background(), ProtocolHeartbeatPartner, conf.GetConfig().Name,
s.Host, s.Streams, map[string]*pp.AddrInfo{address: ad}, 20*time.Second)
2026-02-17 13:11:22 +01:00
}
2026-01-30 16:57:36 +01:00
}
func (s *StreamService) searchPeer(search string) ([]*peer.Peer, error) {
2026-02-03 15:25:15 +01:00
/* TODO FOR TEST ONLY A VARS THAT DEFINE ADDRESS... deserialize */
2026-01-30 16:57:36 +01:00
ps := []*peer.Peer{}
2026-02-03 15:25:15 +01:00
if conf.GetConfig().PeerIDS != "" {
for _, peerID := range strings.Split(conf.GetConfig().PeerIDS, ",") {
2026-02-05 11:23:11 +01:00
ppID := strings.Split(peerID, "/")
fmt.Println(ppID, peerID)
2026-02-03 15:25:15 +01:00
ps = append(ps, &peer.Peer{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: ppID[1],
},
2026-02-05 11:23:11 +01:00
PeerID: ppID[len(ppID)-1],
StreamAddress: peerID,
2026-02-03 15:25:15 +01:00
Relation: peer.PARTNER,
})
}
}
2026-01-30 16:57:36 +01:00
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil)
peers := access.Search(nil, search, false)
for _, p := range peers.Data {
ps = append(ps, p.(*peer.Peer))
}
return ps, nil
}
func (ix *StreamService) Close() {
for _, s := range ix.Streams {
for _, ss := range s {
ss.Stream.Close()
}
}
}
func (s *StreamService) StartGC(interval time.Duration) {
go func() {
t := time.NewTicker(interval)
defer t.Stop()
for range t.C {
s.gc()
}
}()
}
func (s *StreamService) gc() {
2026-02-05 16:17:14 +01:00
s.Mu.Lock()
defer s.Mu.Unlock()
2026-02-03 15:25:15 +01:00
now := time.Now().UTC()
if s.Streams[ProtocolHeartbeatPartner] == nil {
2026-01-30 16:57:36 +01:00
s.Streams[ProtocolHeartbeatPartner] = map[pp.ID]*common.Stream{}
}
2026-02-03 15:25:15 +01:00
streams := s.Streams[ProtocolHeartbeatPartner]
2026-01-30 16:57:36 +01:00
for pid, rec := range streams {
if now.After(rec.Expiry) {
for _, sstreams := range s.Streams {
if sstreams[pid] != nil {
sstreams[pid].Stream.Close()
delete(sstreams, pid)
}
}
}
}
}
2026-02-24 14:31:37 +01:00
func (ps *StreamService) readLoop(s *common.Stream, id pp.ID, proto protocol.ID, protocolInfo *common.ProtocolInfo) {
2026-02-03 15:25:15 +01:00
defer s.Stream.Close()
defer func() {
ps.Mu.Lock()
defer ps.Mu.Unlock()
delete(ps.Streams[proto], id)
}()
loop := true
if !protocolInfo.PersistantStream && !protocolInfo.WaitResponse { // 2 sec is enough... to wait a response
time.AfterFunc(2*time.Second, func() {
loop = false
})
}
2026-01-30 16:57:36 +01:00
for {
if !loop {
break
}
2026-01-30 16:57:36 +01:00
var evt common.Event
2026-02-03 15:25:15 +01:00
if err := json.NewDecoder(s.Stream).Decode(&evt); err != nil {
2026-01-30 16:57:36 +01:00
s.Stream.Close()
2026-02-03 15:25:15 +01:00
continue
2026-01-30 16:57:36 +01:00
}
ps.handleEvent(evt.Type, &evt)
if protocolInfo.WaitResponse && !protocolInfo.PersistantStream {
break
}
2026-01-30 16:57:36 +01:00
}
}
func (abs *StreamService) FilterPeer(peerID string, search string) *dbs.Filters {
id, err := oclib.GetMySelf()
if err != nil {
return nil
}
filter := map[string][]dbs.Filter{
"creator_id": {{Operator: dbs.EQUAL.String(), Value: id}}, // is my resource...
"": {{Operator: dbs.OR.String(), Value: &dbs.Filters{
Or: map[string][]dbs.Filter{
"abstractobject.access_mode": {{Operator: dbs.EQUAL.String(), Value: 1}}, // if public
"abstractinstanciatedresource.instances": {{Operator: dbs.ELEMMATCH.String(), Value: &dbs.Filters{ // or got a partners instances
And: map[string][]dbs.Filter{
"resourceinstance.partnerships": {{Operator: dbs.ELEMMATCH.String(), Value: &dbs.Filters{
And: map[string][]dbs.Filter{
"resourcepartnership.peer_groups." + peerID: {{Operator: dbs.EXISTS.String(), Value: true}},
},
}}},
},
}}},
},
}}},
}
if search != "" {
filter[" "] = []dbs.Filter{{Operator: dbs.OR.String(), Value: &dbs.Filters{
Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
"abstractintanciatedresource.abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
"abstractintanciatedresource.abstractresource.type": {{Operator: dbs.LIKE.String(), Value: search}},
"abstractintanciatedresource.abstractresource.short_description": {{Operator: dbs.LIKE.String(), Value: search}},
"abstractintanciatedresource.abstractresource.description": {{Operator: dbs.LIKE.String(), Value: search}},
"abstractintanciatedresource.abstractresource.owners.name": {{Operator: dbs.LIKE.String(), Value: search}},
"abstractintanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: search}},
},
}}}
}
return &dbs.Filters{
And: filter,
}
}