260 lines
8.4 KiB
Go
260 lines
8.4 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.
|
|
*/
|
|
|
|
package rpcserver
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"google.golang.org/grpc"
|
|
|
|
"d7y.io/dragonfly/v2/cdn/config"
|
|
cdnerrors "d7y.io/dragonfly/v2/cdn/errors"
|
|
"d7y.io/dragonfly/v2/cdn/supervisor"
|
|
"d7y.io/dragonfly/v2/cdn/types"
|
|
"d7y.io/dragonfly/v2/internal/dferrors"
|
|
logger "d7y.io/dragonfly/v2/internal/dflog"
|
|
"d7y.io/dragonfly/v2/internal/idgen"
|
|
"d7y.io/dragonfly/v2/pkg/rpc/base"
|
|
"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem"
|
|
cdnserver "d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/server"
|
|
"d7y.io/dragonfly/v2/pkg/util/digestutils"
|
|
"d7y.io/dragonfly/v2/pkg/util/hostutils"
|
|
"d7y.io/dragonfly/v2/pkg/util/net/urlutils"
|
|
"d7y.io/dragonfly/v2/pkg/util/stringutils"
|
|
)
|
|
|
|
var tracer = otel.Tracer("cdn-server")
|
|
|
|
type server struct {
|
|
*grpc.Server
|
|
taskMgr supervisor.SeedTaskMgr
|
|
cfg *config.Config
|
|
}
|
|
|
|
// New returns a new Manager Object.
|
|
func New(cfg *config.Config, taskMgr supervisor.SeedTaskMgr, opts ...grpc.ServerOption) (*grpc.Server, error) {
|
|
svr := &server{
|
|
taskMgr: taskMgr,
|
|
cfg: cfg,
|
|
}
|
|
|
|
svr.Server = cdnserver.New(svr, opts...)
|
|
return svr.Server, nil
|
|
}
|
|
|
|
func constructRegisterRequest(req *cdnsystem.SeedRequest) (*types.TaskRegisterRequest, error) {
|
|
if err := checkSeedRequestParams(req); err != nil {
|
|
return nil, err
|
|
}
|
|
meta := req.UrlMeta
|
|
header := make(map[string]string)
|
|
if meta != nil {
|
|
if !stringutils.IsBlank(meta.Digest) {
|
|
digest := digestutils.Parse(meta.Digest)
|
|
if _, ok := digestutils.Algorithms[digest[0]]; !ok {
|
|
return nil, errors.Errorf("unsupported digest algorithm")
|
|
}
|
|
header["digest"] = meta.Digest
|
|
}
|
|
if !stringutils.IsBlank(meta.Range) {
|
|
header["range"] = meta.Range
|
|
}
|
|
for k, v := range meta.Header {
|
|
header[k] = v
|
|
}
|
|
}
|
|
return &types.TaskRegisterRequest{
|
|
Header: header,
|
|
URL: req.Url,
|
|
Digest: header["digest"],
|
|
TaskID: req.TaskId,
|
|
Filter: strings.Split(req.UrlMeta.Filter, "&"),
|
|
}, nil
|
|
}
|
|
|
|
// checkSeedRequestParams check the params of SeedRequest.
|
|
func checkSeedRequestParams(req *cdnsystem.SeedRequest) error {
|
|
if !urlutils.IsValidURL(req.Url) {
|
|
return errors.Errorf("resource url: %s is invalid", req.Url)
|
|
}
|
|
if stringutils.IsBlank(req.TaskId) {
|
|
return errors.New("taskId is empty")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (css *server) ObtainSeeds(ctx context.Context, req *cdnsystem.SeedRequest, psc chan<- *cdnsystem.PieceSeed) (err error) {
|
|
var span trace.Span
|
|
ctx, span = tracer.Start(ctx, config.SpanObtainSeeds, trace.WithSpanKind(trace.SpanKindServer))
|
|
defer span.End()
|
|
span.SetAttributes(config.AttributeObtainSeedsRequest.String(req.String()))
|
|
span.SetAttributes(config.AttributeTaskID.String(req.TaskId))
|
|
logger.Infof("obtain seeds request: %+v", req)
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = dferrors.Newf(base.Code_UnknownError, "obtain task(%s) seeds encounter an panic: %v", req.TaskId, r)
|
|
span.RecordError(err)
|
|
logger.WithTaskID(req.TaskId).Errorf("%v", err)
|
|
}
|
|
logger.Infof("seeds task %s result success: %t", req.TaskId, err == nil)
|
|
}()
|
|
registerRequest, err := constructRegisterRequest(req)
|
|
if err != nil {
|
|
err = dferrors.Newf(base.Code_BadRequest, "bad seed request for task(%s): %v", req.TaskId, err)
|
|
span.RecordError(err)
|
|
return err
|
|
}
|
|
// register task
|
|
pieceChan, err := css.taskMgr.Register(ctx, registerRequest)
|
|
if err != nil {
|
|
if cdnerrors.IsResourcesLacked(err) {
|
|
err = dferrors.Newf(base.Code_ResourceLacked, "resources lacked for task(%s): %v", req.TaskId, err)
|
|
span.RecordError(err)
|
|
return err
|
|
}
|
|
err = dferrors.Newf(base.Code_CDNTaskRegistryFail, "failed to register seed task(%s): %v", req.TaskId, err)
|
|
span.RecordError(err)
|
|
return err
|
|
}
|
|
peerID := idgen.CDNPeerID(css.cfg.AdvertiseIP)
|
|
for piece := range pieceChan {
|
|
psc <- &cdnsystem.PieceSeed{
|
|
PeerId: peerID,
|
|
HostUuid: idgen.CDNHostID(hostutils.FQDNHostname, int32(css.cfg.ListenPort)),
|
|
PieceInfo: &base.PieceInfo{
|
|
PieceNum: piece.PieceNum,
|
|
RangeStart: piece.PieceRange.StartIndex,
|
|
RangeSize: piece.PieceLen,
|
|
PieceMd5: piece.PieceMd5,
|
|
PieceOffset: piece.OriginRange.StartIndex,
|
|
PieceStyle: base.PieceStyle(piece.PieceStyle),
|
|
},
|
|
Done: false,
|
|
}
|
|
}
|
|
task, err := css.taskMgr.Get(req.TaskId)
|
|
if err != nil {
|
|
err = dferrors.Newf(base.Code_CDNError, "failed to get task(%s): %v", req.TaskId, err)
|
|
span.RecordError(err)
|
|
return err
|
|
}
|
|
if !task.IsSuccess() {
|
|
err = dferrors.Newf(base.Code_CDNTaskDownloadFail, "task(%s) status error , status: %s", req.TaskId, task.CdnStatus)
|
|
span.RecordError(err)
|
|
return err
|
|
}
|
|
psc <- &cdnsystem.PieceSeed{
|
|
PeerId: peerID,
|
|
HostUuid: idgen.CDNHostID(hostutils.FQDNHostname, int32(css.cfg.ListenPort)),
|
|
Done: true,
|
|
ContentLength: task.SourceFileLength,
|
|
TotalPieceCount: task.PieceTotal,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (css *server) GetPieceTasks(ctx context.Context, req *base.PieceTaskRequest) (piecePacket *base.PiecePacket, err error) {
|
|
var span trace.Span
|
|
ctx, span = tracer.Start(ctx, config.SpanGetPieceTasks, trace.WithSpanKind(trace.SpanKindServer))
|
|
defer span.End()
|
|
span.SetAttributes(config.AttributeGetPieceTasksRequest.String(req.String()))
|
|
span.SetAttributes(config.AttributeTaskID.String(req.TaskId))
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = dferrors.Newf(base.Code_UnknownError, "get task(%s) piece tasks encounter an panic: %v", req.TaskId, r)
|
|
span.RecordError(err)
|
|
logger.WithTaskID(req.TaskId).Errorf("%v", err)
|
|
}
|
|
}()
|
|
logger.Infof("get piece tasks: %+v", req)
|
|
if err := checkPieceTasksRequestParams(req); err != nil {
|
|
err = dferrors.Newf(base.Code_BadRequest, "failed to validate seed request for task(%s): %v", req.TaskId, err)
|
|
span.RecordError(err)
|
|
return nil, err
|
|
}
|
|
task, err := css.taskMgr.Get(req.TaskId)
|
|
if err != nil {
|
|
if cdnerrors.IsDataNotFound(err) {
|
|
err = dferrors.Newf(base.Code_CDNTaskNotFound, "failed to get task(%s) from cdn: %v", req.TaskId, err)
|
|
span.RecordError(err)
|
|
return nil, err
|
|
}
|
|
err = dferrors.Newf(base.Code_CDNError, "failed to get task(%s) from cdn: %v", req.TaskId, err)
|
|
span.RecordError(err)
|
|
return nil, err
|
|
}
|
|
if task.IsError() {
|
|
err = dferrors.Newf(base.Code_CDNTaskDownloadFail, "fail to download task(%s), cdnStatus: %s", task.TaskID, task.CdnStatus)
|
|
span.RecordError(err)
|
|
return nil, err
|
|
}
|
|
pieces, err := css.taskMgr.GetPieces(ctx, req.TaskId)
|
|
if err != nil {
|
|
err = dferrors.Newf(base.Code_CDNError, "failed to get pieces of task(%s) from cdn: %v", task.TaskID, err)
|
|
span.RecordError(err)
|
|
return nil, err
|
|
}
|
|
pieceInfos := make([]*base.PieceInfo, 0)
|
|
var count int32 = 0
|
|
for _, piece := range pieces {
|
|
if piece.PieceNum >= req.StartNum && (count < req.Limit || req.Limit == 0) {
|
|
p := &base.PieceInfo{
|
|
PieceNum: piece.PieceNum,
|
|
RangeStart: piece.PieceRange.StartIndex,
|
|
RangeSize: piece.PieceLen,
|
|
PieceMd5: piece.PieceMd5,
|
|
PieceOffset: piece.OriginRange.StartIndex,
|
|
PieceStyle: base.PieceStyle(piece.PieceStyle),
|
|
}
|
|
pieceInfos = append(pieceInfos, p)
|
|
count++
|
|
}
|
|
}
|
|
pp := &base.PiecePacket{
|
|
TaskId: req.TaskId,
|
|
DstPid: req.DstPid,
|
|
DstAddr: fmt.Sprintf("%s:%d", css.cfg.AdvertiseIP, css.cfg.DownloadPort),
|
|
PieceInfos: pieceInfos,
|
|
TotalPiece: task.PieceTotal,
|
|
ContentLength: task.SourceFileLength,
|
|
PieceMd5Sign: task.PieceMd5Sign,
|
|
}
|
|
span.SetAttributes(config.AttributePiecePacketResult.String(pp.String()))
|
|
return pp, nil
|
|
}
|
|
|
|
func checkPieceTasksRequestParams(req *base.PieceTaskRequest) error {
|
|
if stringutils.IsBlank(req.TaskId) {
|
|
return errors.Wrap(cdnerrors.ErrInvalidValue, "taskId is nil")
|
|
}
|
|
if stringutils.IsBlank(req.SrcPid) {
|
|
return errors.Wrapf(cdnerrors.ErrInvalidValue, "src peer id is nil")
|
|
}
|
|
if req.StartNum < 0 {
|
|
return errors.Wrapf(cdnerrors.ErrInvalidValue, "invalid starNum %d", req.StartNum)
|
|
}
|
|
if req.Limit < 0 {
|
|
return errors.Wrapf(cdnerrors.ErrInvalidValue, "invalid limit %d", req.Limit)
|
|
}
|
|
return nil
|
|
}
|