dragonfly/scheduler/resource/cdn.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
}