332 lines
7.9 KiB
Go
332 lines
7.9 KiB
Go
/*
|
|
* Copyright 2020 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 cdn_mock.go -source cdn.go -package resource
|
|
|
|
package resource
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/pkg/errors"
|
|
"google.golang.org/grpc"
|
|
|
|
logger "d7y.io/dragonfly/v2/internal/dflog"
|
|
"d7y.io/dragonfly/v2/internal/dfnet"
|
|
"d7y.io/dragonfly/v2/pkg/idgen"
|
|
"d7y.io/dragonfly/v2/pkg/rpc/base/common"
|
|
"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem"
|
|
cdnclient "d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/client"
|
|
rpcscheduler "d7y.io/dragonfly/v2/pkg/rpc/scheduler"
|
|
"d7y.io/dragonfly/v2/pkg/util/timeutils"
|
|
"d7y.io/dragonfly/v2/scheduler/config"
|
|
)
|
|
|
|
const (
|
|
// Default value of biz tag for cdn peer
|
|
cdnBizTag = "d7y/cdn"
|
|
)
|
|
|
|
type CDN interface {
|
|
// TriggerTask start to trigger cdn task
|
|
TriggerTask(context.Context, *Task) (*Peer, *rpcscheduler.PeerResult, error)
|
|
|
|
// Client is cdn grpc client
|
|
Client() CDNClient
|
|
}
|
|
|
|
type cdn struct {
|
|
// client is cdn dynamic client
|
|
client CDNClient
|
|
// peerManager is peer manager
|
|
peerManager PeerManager
|
|
// hostManager is host manager
|
|
hostManager HostManager
|
|
}
|
|
|
|
// New cdn interface
|
|
func newCDN(peerManager PeerManager, hostManager HostManager, client CDNClient) CDN {
|
|
return &cdn{
|
|
client: client,
|
|
peerManager: peerManager,
|
|
hostManager: hostManager,
|
|
}
|
|
}
|
|
|
|
// TriggerTask start to trigger cdn task
|
|
func (c *cdn) TriggerTask(ctx context.Context, task *Task) (*Peer, *rpcscheduler.PeerResult, error) {
|
|
stream, err := c.client.ObtainSeeds(ctx, &cdnsystem.SeedRequest{
|
|
TaskId: task.ID,
|
|
Url: task.URL,
|
|
UrlMeta: task.URLMeta,
|
|
})
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var (
|
|
peer *Peer
|
|
initialized bool
|
|
)
|
|
|
|
for {
|
|
piece, 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(PeerEventDownloadFailed); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
return nil, nil, err
|
|
}
|
|
|
|
if !initialized {
|
|
initialized = true
|
|
|
|
// Initialize cdn peer
|
|
peer, err = c.initPeer(task, piece)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
// Handle begin of piece
|
|
if piece.PieceInfo != nil && piece.PieceInfo.PieceNum == common.BeginOfPiece {
|
|
peer.Log.Infof("receive begin of piece from cdn: %#v %#v", piece, piece.PieceInfo)
|
|
if err := peer.FSM.Event(PeerEventDownload); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Handle end of piece
|
|
if piece.Done {
|
|
peer.Log.Infof("receive end of from cdn: %#v %#v", piece, piece.PieceInfo)
|
|
return peer, &rpcscheduler.PeerResult{
|
|
TotalPieceCount: piece.TotalPieceCount,
|
|
ContentLength: piece.ContentLength,
|
|
}, nil
|
|
}
|
|
|
|
// Handle piece download successfully
|
|
peer.Log.Infof("receive piece from cdn: %#v %#v", piece, piece.PieceInfo)
|
|
peer.Pieces.Set(uint(piece.PieceInfo.PieceNum))
|
|
peer.AppendPieceCost(timeutils.SubNano(int64(piece.EndTime), int64(piece.BeginTime)).Milliseconds())
|
|
task.StorePiece(piece.PieceInfo)
|
|
}
|
|
}
|
|
|
|
// Initialize cdn peer
|
|
func (c *cdn) initPeer(task *Task, ps *cdnsystem.PieceSeed) (*Peer, error) {
|
|
// Load peer from manager
|
|
peer, ok := c.peerManager.Load(ps.PeerId)
|
|
if ok {
|
|
return peer, nil
|
|
}
|
|
task.Log.Infof("can not find cdn peer: %s", ps.PeerId)
|
|
|
|
// Load host from manager
|
|
host, ok := c.hostManager.Load(ps.HostUuid)
|
|
if !ok {
|
|
task.Log.Errorf("can not find cdn host uuid: %s", ps.HostUuid)
|
|
return nil, errors.Errorf("can not find host uuid: %s", ps.HostUuid)
|
|
}
|
|
|
|
// New cdn peer
|
|
peer = NewPeer(ps.PeerId, task, host, WithBizTag(cdnBizTag))
|
|
peer.Log.Info("new cdn peer successfully")
|
|
|
|
// Store cdn peer
|
|
c.peerManager.Store(peer)
|
|
peer.Log.Info("cdn peer has been stored")
|
|
|
|
if err := peer.FSM.Event(PeerEventRegisterNormal); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return peer, nil
|
|
}
|
|
|
|
// Client is cdn grpc client
|
|
func (c *cdn) Client() CDNClient {
|
|
return c.client
|
|
}
|
|
|
|
type CDNClient interface {
|
|
// cdnclient is cdn grpc client interface
|
|
cdnclient.CdnClient
|
|
|
|
// Observer is dynconfig observer interface
|
|
config.Observer
|
|
}
|
|
|
|
type cdnClient struct {
|
|
// hostManager is host manager
|
|
hostManager HostManager
|
|
|
|
// cdnClient is cdn grpc client instance
|
|
cdnclient.CdnClient
|
|
|
|
// data is dynconfig data
|
|
data *config.DynconfigData
|
|
}
|
|
|
|
// New cdn client interface
|
|
func newCDNClient(dynconfig config.DynconfigInterface, hostManager HostManager, opts ...grpc.DialOption) (CDNClient, error) {
|
|
config, err := dynconfig.Get()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Initialize cdn grpc client
|
|
client, err := cdnclient.GetClientByAddr(cdnsToNetAddrs(config.CDNs), opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Initialize cdn hosts
|
|
for _, host := range cdnsToHosts(config.CDNs) {
|
|
hostManager.Store(host)
|
|
}
|
|
|
|
dc := &cdnClient{
|
|
hostManager: hostManager,
|
|
CdnClient: client,
|
|
data: config,
|
|
}
|
|
|
|
dynconfig.Register(dc)
|
|
return dc, nil
|
|
}
|
|
|
|
// Dynamic config notify function
|
|
func (c *cdnClient) OnNotify(data *config.DynconfigData) {
|
|
var cdns []config.CDN
|
|
for _, cdn := range data.CDNs {
|
|
cdns = append(cdns, *cdn)
|
|
}
|
|
|
|
if reflect.DeepEqual(c.data, data) {
|
|
logger.Infof("cdn addresses deep equal: %#v", cdns)
|
|
return
|
|
}
|
|
|
|
// If only the ip of the cdn host is changed,
|
|
// the cdn peer needs to be cleared.
|
|
diff := diffCDNs(c.data.CDNs, data.CDNs)
|
|
for _, v := range diff {
|
|
id := idgen.CDNHostID(v.Hostname, v.Port)
|
|
if host, ok := c.hostManager.Load(id); ok {
|
|
host.LeavePeers()
|
|
c.hostManager.Delete(id)
|
|
}
|
|
}
|
|
|
|
// Update host manager
|
|
for _, host := range cdnsToHosts(data.CDNs) {
|
|
c.hostManager.Store(host)
|
|
}
|
|
|
|
// Update dynamic data
|
|
c.data = data
|
|
|
|
// Update grpc cdn addresses
|
|
c.UpdateState(cdnsToNetAddrs(data.CDNs))
|
|
logger.Infof("cdn addresses have been updated: %#v", cdns)
|
|
}
|
|
|
|
// cdnsToHosts coverts []*config.CDN to map[string]*Host.
|
|
func cdnsToHosts(cdns []*config.CDN) map[string]*Host {
|
|
hosts := map[string]*Host{}
|
|
for _, cdn := range cdns {
|
|
var netTopology string
|
|
options := []HostOption{WithIsCDN(true)}
|
|
if config, ok := cdn.GetCDNClusterConfig(); ok && config.LoadLimit > 0 {
|
|
options = append(options, WithUploadLoadLimit(int32(config.LoadLimit)))
|
|
netTopology = config.NetTopology
|
|
}
|
|
|
|
id := idgen.CDNHostID(cdn.Hostname, cdn.Port)
|
|
hosts[id] = NewHost(&rpcscheduler.PeerHost{
|
|
Uuid: id,
|
|
Ip: cdn.IP,
|
|
RpcPort: cdn.Port,
|
|
DownPort: cdn.DownloadPort,
|
|
HostName: cdn.Hostname,
|
|
Idc: cdn.IDC,
|
|
Location: cdn.Location,
|
|
NetTopology: netTopology,
|
|
}, options...)
|
|
}
|
|
return hosts
|
|
}
|
|
|
|
// cdnsToNetAddrs coverts []*config.CDN to []dfnet.NetAddr.
|
|
func cdnsToNetAddrs(cdns []*config.CDN) []dfnet.NetAddr {
|
|
netAddrs := make([]dfnet.NetAddr, 0, len(cdns))
|
|
for _, cdn := range cdns {
|
|
netAddrs = append(netAddrs, dfnet.NetAddr{
|
|
Type: dfnet.TCP,
|
|
Addr: fmt.Sprintf("%s:%d", cdn.IP, cdn.Port),
|
|
})
|
|
}
|
|
|
|
return netAddrs
|
|
}
|
|
|
|
// diffCDNs get cdns with the same HostID but different IP
|
|
func diffCDNs(cx []*config.CDN, cy []*config.CDN) []*config.CDN {
|
|
var diff []*config.CDN
|
|
for _, x := range cx {
|
|
for _, y := range cy {
|
|
if x.Hostname != y.Hostname {
|
|
continue
|
|
}
|
|
|
|
if x.Port != y.Port {
|
|
continue
|
|
}
|
|
|
|
if x.IP == y.IP {
|
|
continue
|
|
}
|
|
|
|
diff = append(diff, x)
|
|
}
|
|
}
|
|
|
|
for _, x := range cx {
|
|
found := false
|
|
for _, y := range cy {
|
|
if idgen.CDNHostID(x.Hostname, x.Port) == idgen.CDNHostID(y.Hostname, y.Port) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
diff = append(diff, x)
|
|
}
|
|
}
|
|
|
|
return diff
|
|
}
|