dragonfly/scheduler/resource/seed_peer.go

249 lines
6.9 KiB
Go

/*
* Copyright 2022 The Dragonfly Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//go:generate mockgen -destination seed_peer_mock.go -source seed_peer.go -package resource
package resource
import (
"context"
"fmt"
"strings"
"time"
cdnsystemv1 "d7y.io/api/pkg/apis/cdnsystem/v1"
commonv1 "d7y.io/api/pkg/apis/common/v1"
commonv2 "d7y.io/api/pkg/apis/common/v2"
schedulerv1 "d7y.io/api/pkg/apis/scheduler/v1"
"d7y.io/dragonfly/v2/pkg/digest"
"d7y.io/dragonfly/v2/pkg/idgen"
"d7y.io/dragonfly/v2/pkg/net/http"
"d7y.io/dragonfly/v2/pkg/rpc/common"
"d7y.io/dragonfly/v2/pkg/types"
"d7y.io/dragonfly/v2/scheduler/metrics"
)
const (
// Default value of seed peer failed timeout.
SeedPeerFailedTimeout = 30 * time.Minute
)
// SeedPeer is the interface used for seed peer.
type SeedPeer interface {
// DownloadTask downloads task back-to-source.
// Used only in v2 version of the grpc.
DownloadTask(context.Context, *Task, types.HostType) error
// TriggerTask triggers the seed peer to download task.
// Used only in v1 version of the grpc.
TriggerTask(context.Context, *http.Range, *Task) (*Peer, *schedulerv1.PeerResult, error)
// Client returns grpc client of seed peer.
Client() SeedPeerClient
// Stop seed peer serivce.
Stop() error
}
// seedPeer contains content for seed peer.
type seedPeer struct {
// client is the dynamic client of seed peer.
client SeedPeerClient
// peerManager is PeerManager interface.
peerManager PeerManager
// hostManager is HostManager interface.
hostManager HostManager
}
// New SeedPeer interface.
func newSeedPeer(client SeedPeerClient, peerManager PeerManager, hostManager HostManager) SeedPeer {
return &seedPeer{
client: client,
peerManager: peerManager,
hostManager: hostManager,
}
}
// TODO Implement DownloadTask
// DownloadTask downloads task back-to-source.
// Used only in v2 version of the grpc.
func (s *seedPeer) DownloadTask(ctx context.Context, task *Task, hostType types.HostType) error {
// ctx, cancel := context.WithCancel(trace.ContextWithSpan(context.Background(), trace.SpanFromContext(ctx)))
// defer cancel()
return nil
}
// TriggerTask triggers the seed peer to download task.
// Used only in v1 version of the grpc.
func (s *seedPeer) TriggerTask(ctx context.Context, rg *http.Range, task *Task) (*Peer, *schedulerv1.PeerResult, error) {
urlMeta := &commonv1.UrlMeta{
Tag: task.Tag,
Filter: strings.Join(task.Filters, idgen.URLFilterSeparator),
Header: task.Header,
Application: task.Application,
Priority: commonv1.Priority_LEVEL0,
}
if task.Digest != nil {
urlMeta.Digest = task.Digest.String()
}
if rg != nil {
urlMeta.Range = rg.URLMetaString()
}
stream, err := s.client.ObtainSeeds(ctx, &cdnsystemv1.SeedRequest{
TaskId: task.ID,
Url: task.URL,
UrlMeta: urlMeta,
})
if err != nil {
return nil, nil, err
}
var (
peer *Peer
initialized bool
)
for {
pieceSeed, err := stream.Recv()
if err != nil {
// If the peer initialization succeeds and the download fails,
// set peer status is PeerStateFailed.
if peer != nil {
if err := peer.FSM.Event(ctx, PeerEventDownloadFailed); err != nil {
return nil, nil, err
}
}
return nil, nil, err
}
if !initialized {
initialized = true
// Initialize seed peer.
peer, err = s.initSeedPeer(ctx, rg, task, pieceSeed)
if err != nil {
return nil, nil, err
}
}
if pieceSeed.PieceInfo != nil {
// Handle begin of piece.
if pieceSeed.PieceInfo.PieceNum == common.BeginOfPiece {
peer.Log.Infof("receive begin of piece from seed peer: %#v %#v", pieceSeed, pieceSeed.PieceInfo)
if err := peer.FSM.Event(ctx, PeerEventDownload); err != nil {
return nil, nil, err
}
continue
}
// Handle piece download successfully.
peer.Log.Infof("receive piece from seed peer: %#v %#v", pieceSeed, pieceSeed.PieceInfo)
cost := time.Duration(int64(pieceSeed.PieceInfo.DownloadCost) * int64(time.Millisecond))
piece := &Piece{
Number: pieceSeed.PieceInfo.PieceNum,
Offset: pieceSeed.PieceInfo.RangeStart,
Length: uint64(pieceSeed.PieceInfo.RangeSize),
TrafficType: commonv2.TrafficType_BACK_TO_SOURCE,
Cost: cost,
CreatedAt: time.Now().Add(-cost),
}
if len(pieceSeed.PieceInfo.PieceMd5) > 0 {
piece.Digest = digest.New(digest.AlgorithmMD5, pieceSeed.PieceInfo.PieceMd5)
}
peer.StorePiece(piece)
peer.FinishedPieces.Set(uint(pieceSeed.PieceInfo.PieceNum))
peer.AppendPieceCost(piece.Cost)
// When the piece is downloaded successfully,
// peer.UpdatedAt needs to be updated to prevent
// the peer from being GC during the download process.
peer.UpdatedAt.Store(time.Now())
peer.PieceUpdatedAt.Store(time.Now())
task.StorePiece(piece)
// Collect Traffic metrics.
trafficType := commonv2.TrafficType_BACK_TO_SOURCE
if pieceSeed.Reuse {
trafficType = commonv2.TrafficType_LOCAL_PEER
}
metrics.Traffic.WithLabelValues(trafficType.String(), peer.Task.Type.String(),
peer.Task.Tag, peer.Task.Application, peer.Host.Type.Name()).Add(float64(pieceSeed.PieceInfo.RangeSize))
}
// Handle end of piece.
if pieceSeed.Done {
peer.Log.Infof("receive done piece")
return peer, &schedulerv1.PeerResult{
TotalPieceCount: pieceSeed.TotalPieceCount,
ContentLength: pieceSeed.ContentLength,
}, nil
}
}
}
// Initialize seed peer.
func (s *seedPeer) initSeedPeer(ctx context.Context, rg *http.Range, task *Task, ps *cdnsystemv1.PieceSeed) (*Peer, error) {
// Load host from manager.
host, loaded := s.hostManager.Load(ps.HostId)
if !loaded {
task.Log.Errorf("can not find seed host id: %s", ps.HostId)
return nil, fmt.Errorf("can not find host id: %s", ps.HostId)
}
host.UpdatedAt.Store(time.Now())
// Load peer from manager.
peer, loaded := s.peerManager.Load(ps.PeerId)
if loaded {
return peer, nil
}
task.Log.Infof("can not find seed peer: %s", ps.PeerId)
options := []PeerOption{}
if rg != nil {
options = append(options, WithRange(*rg))
}
// New and store seed peer without range.
peer = NewPeer(ps.PeerId, task, host, options...)
s.peerManager.Store(peer)
peer.Log.Info("seed peer has been stored")
if err := peer.FSM.Event(ctx, PeerEventRegisterNormal); err != nil {
return nil, err
}
return peer, nil
}
// Client is seed peer grpc client.
func (s *seedPeer) Client() SeedPeerClient {
return s.client
}
// Stop seed peer serivce.
func (s *seedPeer) Stop() error {
return s.client.Close()
}