306 lines
9.9 KiB
Go
306 lines
9.9 KiB
Go
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 ProtocolConsidersResource = "/opencloud/resource/considers/1.0"
|
|
const ProtocolMinioConfigResource = "/opencloud/minio/config/1.0"
|
|
const ProtocolAdmiraltyConfigResource = "/opencloud/admiralty/config/1.0"
|
|
|
|
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 ProtocolSendPlanner = "/opencloud/resource/planner/1.0"
|
|
const ProtocolVerifyResource = "/opencloud/resource/verify/1.0"
|
|
const ProtocolHeartbeatPartner = "/opencloud/resource/heartbeat/partner/1.0"
|
|
|
|
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},
|
|
}
|
|
|
|
var protocolsPartners = map[protocol.ID]*common.ProtocolInfo{
|
|
ProtocolCreateResource: {TTL: 3 * time.Second},
|
|
ProtocolUpdateResource: {TTL: 3 * time.Second},
|
|
ProtocolDeleteResource: {TTL: 3 * time.Second},
|
|
}
|
|
|
|
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{}
|
|
}
|
|
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,
|
|
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()])
|
|
}
|
|
|
|
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, json.NewDecoder(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)
|
|
}
|
|
}
|
|
// GC is already running via InitStream — starting a new ticker goroutine on
|
|
// every heartbeat would leak an unbounded number of goroutines.
|
|
}
|
|
|
|
func (s *StreamService) connectToPartners() error {
|
|
logger := oclib.GetLogger()
|
|
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)
|
|
}
|
|
logger.Info().Msg("SetStreamHandler " + string(proto))
|
|
s.Host.SetStreamHandler(proto, f)
|
|
}
|
|
peers, err := s.searchPeer(fmt.Sprintf("%v", peer.PARTNER.EnumIndex()))
|
|
if err != nil {
|
|
logger.Err(err)
|
|
return err
|
|
}
|
|
for _, p := range peers {
|
|
s.ConnectToPartner(p.StreamAddress)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *StreamService) ConnectToPartner(address string) {
|
|
logger := oclib.GetLogger()
|
|
if ad, err := pp.AddrInfoFromString(address); err == nil {
|
|
logger.Info().Msg("Connect to Partner " + ProtocolHeartbeatPartner + " " + address)
|
|
common.SendHeartbeat(context.Background(), ProtocolHeartbeatPartner, conf.GetConfig().Name,
|
|
s.Host, s.Streams, map[string]*pp.AddrInfo{address: ad}, nil, 20*time.Second)
|
|
}
|
|
}
|
|
|
|
func (s *StreamService) searchPeer(search string) ([]*peer.Peer, error) {
|
|
ps := []*peer.Peer{}
|
|
if conf.GetConfig().PeerIDS != "" {
|
|
for _, peerID := range strings.Split(conf.GetConfig().PeerIDS, ",") {
|
|
ppID := strings.Split(peerID, "/")
|
|
ps = append(ps, &peer.Peer{
|
|
AbstractObject: utils.AbstractObject{
|
|
UUID: uuid.New().String(),
|
|
Name: ppID[1],
|
|
},
|
|
PeerID: ppID[len(ppID)-1],
|
|
StreamAddress: peerID,
|
|
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 *common.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 {
|
|
// Any decode error (EOF, reset, malformed JSON) terminates the loop;
|
|
// continuing on a dead/closed stream creates an infinite spin.
|
|
return
|
|
}
|
|
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,
|
|
}
|
|
}
|