package stream import ( "context" "encoding/json" "fmt" "oc-discovery/conf" "oc-discovery/daemons/node/common" "strings" "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" "cloud.o-forge.io/core/oc-lib/models/utils" "github.com/google/uuid" "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" ma "github.com/multiformats/go-multiaddr" ) 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" const ProtocolVerifyResource = "/opencloud/resource/verify/1.0" const ProtocolHeartbeatPartner = "/opencloud/resource/heartbeat/partner/1.0" type ProtocolInfo struct { PersistantStream bool WaitResponse bool } var protocols = map[protocol.ID]*ProtocolInfo{ ProtocolSearchResource: {WaitResponse: true}, ProtocolVerifyResource: {WaitResponse: true}, } var protocolsPartners = map[protocol.ID]*ProtocolInfo{ ProtocolCreateResource: {}, ProtocolUpdateResource: {}, ProtocolDeleteResource: {}, } type StreamService struct { Key pp.ID Host host.Host Node common.DiscoveryPeer Streams common.ProtocolStream maxNodesConn int Mu sync.RWMutex // 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) { logger := oclib.GetLogger() service := &StreamService{ Key: key, Node: node, Host: h, Streams: common.ProtocolStream{}, maxNodesConn: maxNode, } logger.Info().Msg("handle to partner heartbeat protocol...") service.Host.SetStreamHandler(ProtocolHeartbeatPartner, service.HandlePartnerHeartbeat) for proto := range protocols { service.Host.SetStreamHandler(proto, service.HandleResponse) } logger.Info().Msg("connect to partners...") service.connectToPartners() // we set up a stream go service.StartGC(8 * time.Second) 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{} } s.Streams[stream.Protocol()][stream.Conn().RemotePeer()] = &common.Stream{ Stream: stream, Expiry: time.Now().UTC().Add(1 * time.Minute), } s.Mu.Unlock() go s.readLoop(s.Streams[stream.Protocol()][stream.Conn().RemotePeer()], stream.Conn().RemotePeer(), stream.Protocol(), protocols[stream.Protocol()]) } func (s *StreamService) HandlePartnerHeartbeat(stream network.Stream) { s.Mu.Lock() if s.Streams[ProtocolHeartbeatPartner] == nil { s.Streams[ProtocolHeartbeatPartner] = map[pp.ID]*common.Stream{} } streams := s.Streams[ProtocolHeartbeatPartner] 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() // 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) } else { // if not in stream ? val, err := stream.Conn().RemoteMultiaddr().ValueForProtocol(ma.P_IP4) if err == nil { s.ConnectToPartner(val) } } go s.StartGC(30 * time.Second) } func (s *StreamService) connectToPartners() error { for proto, info := range protocolsPartners { 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), } go s.readLoop(s.Streams[proto][ss.Conn().RemotePeer()], ss.Conn().RemotePeer(), proto, info) } fmt.Println("SetStreamHandler", proto) s.Host.SetStreamHandler(proto, f) } peers, err := s.searchPeer(fmt.Sprintf("%v", peer.PARTNER.EnumIndex())) if err != nil { return err } for _, p := range peers { s.ConnectToPartner(p.StreamAddress) } return nil } 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) } } func (s *StreamService) searchPeer(search string) ([]*peer.Peer, error) { /* TODO FOR TEST ONLY A VARS THAT DEFINE ADDRESS... deserialize */ ps := []*peer.Peer{} if conf.GetConfig().PeerIDS != "" { for _, peerID := range strings.Split(conf.GetConfig().PeerIDS, ",") { ppID := strings.Split(peerID, "/") fmt.Println(ppID, peerID) ps = append(ps, &peer.Peer{ AbstractObject: utils.AbstractObject{ UUID: uuid.New().String(), Name: ppID[1], }, PeerID: ppID[len(ppID)-1], StreamAddress: peerID, State: peer.ONLINE, Relation: peer.PARTNER, }) } } 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() { s.Mu.Lock() defer s.Mu.Unlock() now := time.Now().UTC() if s.Streams[ProtocolHeartbeatPartner] == nil { s.Streams[ProtocolHeartbeatPartner] = map[pp.ID]*common.Stream{} } streams := s.Streams[ProtocolHeartbeatPartner] 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) } } } } } func (ps *StreamService) readLoop(s *common.Stream, id pp.ID, proto protocol.ID, protocolInfo *ProtocolInfo) { 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 }) } for { if !loop { break } var evt common.Event if err := json.NewDecoder(s.Stream).Decode(&evt); err != nil { s.Stream.Close() continue } ps.handleEvent(evt.Type, &evt) if protocolInfo.WaitResponse && !protocolInfo.PersistantStream { break } } } 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, } }